Browse Source

辅助包

WH01243 3 years ago
parent
commit
599fea3e60
100 changed files with 17585 additions and 0 deletions
  1. 4 0
      spirit/.gitignore
  2. 1 0
      spirit/README.md
  3. 8 0
      spirit/common/src/github.com/GeertJohan/go.rice/.gitignore
  4. 16 0
      spirit/common/src/github.com/GeertJohan/go.rice/.travis.yml
  5. 4 0
      spirit/common/src/github.com/GeertJohan/go.rice/AUTHORS
  6. 22 0
      spirit/common/src/github.com/GeertJohan/go.rice/LICENSE
  7. 142 0
      spirit/common/src/github.com/GeertJohan/go.rice/README.md
  8. 141 0
      spirit/common/src/github.com/GeertJohan/go.rice/appended.go
  9. 337 0
      spirit/common/src/github.com/GeertJohan/go.rice/box.go
  10. 39 0
      spirit/common/src/github.com/GeertJohan/go.rice/config.go
  11. 136 0
      spirit/common/src/github.com/GeertJohan/go.rice/config_test.go
  12. 4 0
      spirit/common/src/github.com/GeertJohan/go.rice/debug.go
  13. 90 0
      spirit/common/src/github.com/GeertJohan/go.rice/embedded.go
  14. 80 0
      spirit/common/src/github.com/GeertJohan/go.rice/embedded/embedded.go
  15. 2 0
      spirit/common/src/github.com/GeertJohan/go.rice/example/example-files/file.txt
  16. BIN
      spirit/common/src/github.com/GeertJohan/go.rice/example/example-files/img/doge.jpg
  17. 1 0
      spirit/common/src/github.com/GeertJohan/go.rice/example/example-templates/message.tmpl
  18. 69 0
      spirit/common/src/github.com/GeertJohan/go.rice/example/example.go
  19. 171 0
      spirit/common/src/github.com/GeertJohan/go.rice/file.go
  20. 21 0
      spirit/common/src/github.com/GeertJohan/go.rice/http.go
  21. 157 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/append.go
  22. 33 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/clean.go
  23. 181 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/embed-go.go
  24. 134 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/embed-go_test.go
  25. 204 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/embed-syso.go
  26. 148 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/find.go
  27. 268 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/find_test.go
  28. 82 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/flags.go
  29. 646 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/helpers_test.go
  30. 14 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/identifier.go
  31. 87 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/main.go
  32. 177 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/templates.go
  33. 22 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/util.go
  34. 42 0
      spirit/common/src/github.com/GeertJohan/go.rice/rice/writecoff.go
  35. 19 0
      spirit/common/src/github.com/GeertJohan/go.rice/sort.go
  36. 304 0
      spirit/common/src/github.com/GeertJohan/go.rice/virtual.go
  37. 122 0
      spirit/common/src/github.com/GeertJohan/go.rice/walk.go
  38. 1 0
      spirit/common/src/github.com/PuerkitoBio/goquery/.gitattributes
  39. 16 0
      spirit/common/src/github.com/PuerkitoBio/goquery/.gitignore
  40. 17 0
      spirit/common/src/github.com/PuerkitoBio/goquery/.travis.yml
  41. 12 0
      spirit/common/src/github.com/PuerkitoBio/goquery/LICENSE
  42. 181 0
      spirit/common/src/github.com/PuerkitoBio/goquery/README.md
  43. 124 0
      spirit/common/src/github.com/PuerkitoBio/goquery/array.go
  44. 234 0
      spirit/common/src/github.com/PuerkitoBio/goquery/array_test.go
  45. 436 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.1.0
  46. 438 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.1.1
  47. 405 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.1.1-v0.2.1-go1.1rc1.svg
  48. 459 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.2.0
  49. 420 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.2.0-v0.2.1-go1.1rc1.svg
  50. 470 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.2.1-go1.1rc1
  51. 476 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.3.0
  52. 478 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.3.2-go1.2
  53. 477 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.3.2-go1.2-take2
  54. 477 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.3.2-go1.2rc1
  55. 85 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v1.0.0-go1.7
  56. 85 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v1.0.1a-go1.7
  57. 85 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v1.0.1b-go1.7
  58. 86 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench/v1.0.1c-go1.7
  59. 120 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench_array_test.go
  60. 40 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench_example_test.go
  61. 104 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench_expand_test.go
  62. 236 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench_filter_test.go
  63. 68 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench_iteration_test.go
  64. 51 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench_property_test.go
  65. 111 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench_query_test.go
  66. 802 0
      spirit/common/src/github.com/PuerkitoBio/goquery/bench_traversal_test.go
  67. 123 0
      spirit/common/src/github.com/PuerkitoBio/goquery/doc.go
  68. 68 0
      spirit/common/src/github.com/PuerkitoBio/goquery/doc/tips.md
  69. 82 0
      spirit/common/src/github.com/PuerkitoBio/goquery/example_test.go
  70. 70 0
      spirit/common/src/github.com/PuerkitoBio/goquery/expand.go
  71. 118 0
      spirit/common/src/github.com/PuerkitoBio/goquery/expand_test.go
  72. 163 0
      spirit/common/src/github.com/PuerkitoBio/goquery/filter.go
  73. 206 0
      spirit/common/src/github.com/PuerkitoBio/goquery/filter_test.go
  74. 6 0
      spirit/common/src/github.com/PuerkitoBio/goquery/go.mod
  75. 5 0
      spirit/common/src/github.com/PuerkitoBio/goquery/go.sum
  76. 39 0
      spirit/common/src/github.com/PuerkitoBio/goquery/iteration.go
  77. 88 0
      spirit/common/src/github.com/PuerkitoBio/goquery/iteration_test.go
  78. 574 0
      spirit/common/src/github.com/PuerkitoBio/goquery/manipulation.go
  79. 513 0
      spirit/common/src/github.com/PuerkitoBio/goquery/manipulation_test.go
  80. 37 0
      spirit/common/src/github.com/PuerkitoBio/goquery/misc/git/pre-commit
  81. 275 0
      spirit/common/src/github.com/PuerkitoBio/goquery/property.go
  82. 252 0
      spirit/common/src/github.com/PuerkitoBio/goquery/property_test.go
  83. 49 0
      spirit/common/src/github.com/PuerkitoBio/goquery/query.go
  84. 103 0
      spirit/common/src/github.com/PuerkitoBio/goquery/query_test.go
  85. 855 0
      spirit/common/src/github.com/PuerkitoBio/goquery/testdata/gotesting.html
  86. 26 0
      spirit/common/src/github.com/PuerkitoBio/goquery/testdata/gowiki.html
  87. 413 0
      spirit/common/src/github.com/PuerkitoBio/goquery/testdata/metalreview.html
  88. 102 0
      spirit/common/src/github.com/PuerkitoBio/goquery/testdata/page.html
  89. 24 0
      spirit/common/src/github.com/PuerkitoBio/goquery/testdata/page2.html
  90. 24 0
      spirit/common/src/github.com/PuerkitoBio/goquery/testdata/page3.html
  91. 698 0
      spirit/common/src/github.com/PuerkitoBio/goquery/traversal.go
  92. 793 0
      spirit/common/src/github.com/PuerkitoBio/goquery/traversal_test.go
  93. 141 0
      spirit/common/src/github.com/PuerkitoBio/goquery/type.go
  94. 202 0
      spirit/common/src/github.com/PuerkitoBio/goquery/type_test.go
  95. 161 0
      spirit/common/src/github.com/PuerkitoBio/goquery/utilities.go
  96. 128 0
      spirit/common/src/github.com/PuerkitoBio/goquery/utilities_test.go
  97. 2 0
      spirit/common/src/github.com/alecthomas/log4go/.gitignore
  98. 13 0
      spirit/common/src/github.com/alecthomas/log4go/LICENSE
  99. 14 0
      spirit/common/src/github.com/alecthomas/log4go/README
  100. 296 0
      spirit/common/src/github.com/alecthomas/log4go/config.go

+ 4 - 0
spirit/.gitignore

@@ -0,0 +1,4 @@
+.DS_Store
+common/.idea
+common/pkg
+.idea/

+ 1 - 0
spirit/README.md

@@ -0,0 +1 @@
+test

+ 8 - 0
spirit/common/src/github.com/GeertJohan/go.rice/.gitignore

@@ -0,0 +1,8 @@
+/example/example
+/example/example.exe
+/rice/rice
+/rice/rice.exe
+
+*.rice-box.go
+*.rice-box.syso
+.wercker

+ 16 - 0
spirit/common/src/github.com/GeertJohan/go.rice/.travis.yml

@@ -0,0 +1,16 @@
+language: go
+
+go:
+    - master
+    - 1.11.x
+    - 1.10.x
+    - 1.9.x
+    - 1.8.x
+
+install:
+    - go get -t ./...
+    - env
+script:
+    - go build -x ./...
+    - go test -cover ./...
+    - go vet ./...

+ 4 - 0
spirit/common/src/github.com/GeertJohan/go.rice/AUTHORS

@@ -0,0 +1,4 @@
+Geert-Johan Riemer <geertjohan@geertjohan.net>
+Paul Maddox <paul.maddox@gmail.com>
+Vincent Petithory <vincent.petithory@gmail.com>
+

+ 22 - 0
spirit/common/src/github.com/GeertJohan/go.rice/LICENSE

@@ -0,0 +1,22 @@
+Copyright (c) 2013, Geert-Johan Riemer
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 142 - 0
spirit/common/src/github.com/GeertJohan/go.rice/README.md

@@ -0,0 +1,142 @@
+# go.rice
+
+[![Build Status](https://travis-ci.org/GeertJohan/go.rice.png)](https://travis-ci.org/GeertJohan/go.rice)
+[![Godoc](https://img.shields.io/badge/godoc-go.rice-blue.svg?style=flat-square)](https://godoc.org/github.com/GeertJohan/go.rice)
+
+go.rice is a [Go](http://golang.org) package that makes working with resources such as html,js,css,images and templates easy. During development `go.rice` will load required files directly from disk. Upon deployment it's easy to add all resource files to a executable using the `rice` tool, without changing the source code for your package. go.rice provides methods to add resources to a binary in different scenarios.
+
+## What does it do
+
+The first thing go.rice does is finding the correct absolute path for your resource files. Say you are executing a binary in your home directory, but your `html-files` are in `$GOPATH/src/yourApplication/html-files`. `go.rice` will lookup the correct path for that directory (relative to the location of yourApplication). All you have to do is include the resources using `rice.FindBox("html-files")`.
+
+This works fine when the source is available to the machine executing the binary, which is the case when installing the executalbe with `go get` or `go install`. But it does not work when you wish to provide a single binary without source. This is where the `rice` tool comes in. It analyses source code and finds call's to `rice.FindBox(..)`. Then it adds the required directories to the executable binary, There are two strategies to do this. You can 'embed' the assets by generating go source code and then compile them into the executable binary, or you can 'append' the assets to the executable binary after compiling. In both cases the `rice.FindBox(..)` call detects the embedded or appended resources and load those, instead of looking up files from disk.
+
+## Installation
+
+Use `go get` to install the package the `rice` tool.
+
+```bash
+go get github.com/GeertJohan/go.rice
+go get github.com/GeertJohan/go.rice/rice
+```
+
+## Package usage
+
+Import the package: `import "github.com/GeertJohan/go.rice"`
+
+Serving a static content folder over HTTP with a rice Box:
+
+```go
+http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
+http.ListenAndServe(":8080", nil)
+```
+
+Serve a static content folder over HTTP at a non-root location:
+
+```go
+box := rice.MustFindBox("cssfiles")
+cssFileServer := http.StripPrefix("/css/", http.FileServer(box.HTTPBox()))
+http.Handle("/css/", cssFileServer)
+http.ListenAndServe(":8080", nil)
+```
+
+Note the *trailing slash* in `/css/` in both the call to
+`http.StripPrefix` and `http.Handle`.
+
+Loading a template:
+
+```go
+// find a rice.Box
+templateBox, err := rice.FindBox("example-templates")
+if err != nil {
+	log.Fatal(err)
+}
+// get file contents as string
+templateString, err := templateBox.String("message.tmpl")
+if err != nil {
+	log.Fatal(err)
+}
+// parse and execute the template
+tmplMessage, err := template.New("message").Parse(templateString)
+if err != nil {
+	log.Fatal(err)
+}
+tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
+
+```
+
+Never call `FindBox()` or `MustFindBox()` from an `init()` function, as there is no guarantee the boxes are loaded at that time.
+
+### Calling FindBox and MustFindBox
+
+Always call `FindBox()` or `MustFindBox()` with string literals e.g. `FindBox("example")`. Do not use string constants or variables. This will prevent the rice tool to fail with error `Error: found call to rice.FindBox, but argument must be a string literal.`.
+
+## Tool usage
+
+The `rice` tool lets you add the resources to a binary executable so the files are not loaded from the filesystem anymore. This creates a 'standalone' executable. There are multiple strategies to add the resources and assets to a binary, each has pro's and con's but all will work without requiring changes to the way you load the resources.
+
+### `rice embed-go`: Embed resources by generating Go source code
+
+Execute this method before building. It generates a single Go source file called *rice-box.go* for each package. The generated go file contains all assets. The Go tool compiles this into the binary.
+
+The downside with this option is that the generated go source file can become large, which may slow down compilation and requires more memory to compile.
+
+Execute the following commands:
+
+```bash
+rice embed-go
+go build
+```
+
+*A Note on Symbolic Links*: `embed-go` uses the `os.Walk` function from the standard library.  The `os.Walk` function does **not** follow symbolic links. When creating a box, be aware that any symbolic links inside your box's directory are not followed. When the box itself is a symbolic link, the rice tool resolves its actual location before adding the contents.
+
+### `rice embed-syso`: Embed resources by generating a coff .syso file and some .go source code
+
+** This method is experimental. Do not use for production systems. **
+
+Execute this method before building. It generates a COFF .syso file and Go source file. The Go compiler then compiles these files into the binary.
+
+Execute the following commands:
+
+```bash
+rice embed-syso
+go build
+```
+
+### `rice append`: Append resources to executable as zip file
+
+This method changes an already built executable. It appends the resources as zip file to the binary. It makes compilation a lot faster. Using the append method works great for adding large assets to an executable binary.
+
+A downside for appending is that it does not provide a working Seek method.
+
+Run the following commands to create a standalone executable.
+
+```bash
+go build -o example
+rice append --exec example
+```
+
+## Help information
+
+Run `rice --help` for information about all flags and subcommands.
+
+You can use the `--help` flag on each sub-command. For example: `rice append --help`.
+
+## Order of precedence
+
+When opening a new box, the `rice.FindBox(..)` tries to locate the resources in the following order:
+
+- embedded (generated as `rice-box.go`)
+- appended (appended to the binary executable after compiling)
+- 'live' from filesystem
+
+## License
+
+This project is licensed under a Simplified BSD license. Please read the [LICENSE file][license].
+
+## Package documentation
+
+You will find package documentation at [godoc.org/github.com/GeertJohan/go.rice][godoc].
+
+[license]: https://github.com/GeertJohan/go.rice/blob/master/LICENSE
+[godoc]: http://godoc.org/github.com/GeertJohan/go.rice

+ 141 - 0
spirit/common/src/github.com/GeertJohan/go.rice/appended.go

@@ -0,0 +1,141 @@
+package rice
+
+import (
+	"archive/zip"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/daaku/go.zipexe"
+)
+
+// 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 := os.Executable()
+	if err != nil {
+		return // not appended or cant find self executable
+	}
+	thisFile, err = filepath.EvalSymlinks(thisFile)
+	if err != nil {
+		return
+	}
+	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
+}

+ 337 - 0
spirit/common/src/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
+}

+ 39 - 0
spirit/common/src/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
+}

+ 136 - 0
spirit/common/src/github.com/GeertJohan/go.rice/config_test.go

@@ -0,0 +1,136 @@
+package rice
+
+import (
+	"fmt"
+	"io/ioutil"
+	"testing"
+
+	"github.com/GeertJohan/go.rice/embedded"
+)
+
+// For all test code in this package, define a set of test boxes.
+var eb1 *embedded.EmbeddedBox
+var ab1, ab2 *appendedBox
+var fsb1, fsb2, fsb3 string // paths to filesystem boxes
+func init() {
+	var err error
+
+	// Box1 exists in all three locations.
+	eb1 = &embedded.EmbeddedBox{Name: "box1"}
+	embedded.RegisterEmbeddedBox(eb1.Name, eb1)
+	ab1 = &appendedBox{Name: "box1"}
+	appendedBoxes["box1"] = ab1
+	fsb1, err = ioutil.TempDir("", "box1")
+	if err != nil {
+		panic(err)
+	}
+
+	// Box2 exists in only appended and FS.
+	ab2 = &appendedBox{Name: "box2"}
+	appendedBoxes["box2"] = ab2
+	fsb2, err = ioutil.TempDir("", "box2")
+	if err != nil {
+		panic(err)
+	}
+
+	// Box3 exists only on disk.
+	fsb3, err = ioutil.TempDir("", "box3")
+	if err != nil {
+		panic(err)
+	}
+
+	// Also, replace the default filesystem lookup path to directly support the
+	// on-disk temp directories.
+	resolveAbsolutePathFromCaller = func(name string, n int) (string, error) {
+		if name == "box1" {
+			return fsb1, nil
+		} else if name == "box2" {
+			return fsb2, nil
+		} else if name == "box3" {
+			return fsb3, nil
+		}
+		return "", fmt.Errorf("Unknown box name: %q", name)
+	}
+}
+
+func TestDefaultLookupOrder(t *testing.T) {
+	// Box1 exists in all three, so the default order should find the embedded.
+	b, err := FindBox("box1")
+	if err != nil {
+		t.Fatalf("Expected to find box1, got error: %v", err)
+	}
+	if b.embed != eb1 {
+		t.Fatalf("Expected to find embedded box, but got %#v", b)
+	}
+
+	// Box2 exists in appended and FS, so find the appended.
+	b2, err := FindBox("box2")
+	if err != nil {
+		t.Fatalf("Expected to find box2, got error: %v", err)
+	}
+	if b2.appendd != ab2 {
+		t.Fatalf("Expected to find appended box, but got %#v", b2)
+	}
+
+	// Box3 exists only on FS, so find it there.
+	b3, err := FindBox("box3")
+	if err != nil {
+		t.Fatalf("Expected to find box3, got error: %v", err)
+	}
+	if b3.absolutePath != fsb3 {
+		t.Fatalf("Expected to find FS box, but got %#v", b3)
+	}
+}
+
+func TestConfigLocateOrder(t *testing.T) {
+	cfg := Config{LocateOrder: []LocateMethod{LocateFS, LocateAppended, LocateEmbedded}}
+	fsb := []string{fsb1, fsb2, fsb3}
+	// All 3 boxes have a FS backend, so we should always find that.
+	for i, boxName := range []string{"box1", "box2", "box3"} {
+		b, err := cfg.FindBox(boxName)
+		if err != nil {
+			t.Fatalf("Expected to find %q, got error: %v", boxName, err)
+		}
+		if b.absolutePath != fsb[i] {
+			t.Fatalf("Expected to find FS box, but got %#v", b)
+		}
+	}
+
+	cfg.LocateOrder = []LocateMethod{LocateAppended, LocateFS, LocateEmbedded}
+	{
+		b, err := cfg.FindBox("box3")
+		if err != nil {
+			t.Fatalf("Expected to find box3, got error: %v", err)
+		}
+		if b.absolutePath != fsb3 {
+			t.Fatalf("Expected to find FS box, but got %#v", b)
+		}
+	}
+	{
+		b, err := cfg.FindBox("box2")
+		if err != nil {
+			t.Fatalf("Expected to find box2, got error: %v", err)
+		}
+		if b.appendd != ab2 {
+			t.Fatalf("Expected to find appended box, but got %#v", b)
+		}
+	}
+
+	// What if we don't list all the locate methods?
+	cfg.LocateOrder = []LocateMethod{LocateEmbedded}
+	{
+		b, err := cfg.FindBox("box2")
+		if err == nil {
+			t.Fatalf("Expected not to find box2, but something was found: %#v", b)
+		}
+	}
+	{
+		b, err := cfg.FindBox("box1")
+		if err != nil {
+			t.Fatalf("Expected to find box2, got error: %v", err)
+		}
+		if b.embed != eb1 {
+			t.Fatalf("Expected to find embedded box, but got %#v", b)
+		}
+	}
+}

+ 4 - 0
spirit/common/src/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

+ 90 - 0
spirit/common/src/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
+}

+ 80 - 0
spirit/common/src/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
+}

+ 2 - 0
spirit/common/src/github.com/GeertJohan/go.rice/example/example-files/file.txt

@@ -0,0 +1,2 @@
+test content
+break

BIN
spirit/common/src/github.com/GeertJohan/go.rice/example/example-files/img/doge.jpg


+ 1 - 0
spirit/common/src/github.com/GeertJohan/go.rice/example/example-templates/message.tmpl

@@ -0,0 +1 @@
+I have a message for you: {{.Message}}

+ 69 - 0
spirit/common/src/github.com/GeertJohan/go.rice/example/example.go

@@ -0,0 +1,69 @@
+package main
+
+import (
+	"encoding/hex"
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"text/template"
+
+	"github.com/GeertJohan/go.rice"
+	"github.com/davecgh/go-spew/spew"
+)
+
+func main() {
+	conf := rice.Config{
+		LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS},
+	}
+	box, err := conf.FindBox("example-files")
+	if err != nil {
+		log.Fatalf("error opening rice.Box: %s\n", err)
+	}
+	// spew.Dump(box)
+
+	contentString, err := box.String("file.txt")
+	if err != nil {
+		log.Fatalf("could not read file contents as string: %s\n", err)
+	}
+	log.Printf("Read some file contents as string:\n%s\n", contentString)
+
+	contentBytes, err := box.Bytes("file.txt")
+	if err != nil {
+		log.Fatalf("could not read file contents as byteSlice: %s\n", err)
+	}
+	log.Printf("Read some file contents as byteSlice:\n%s\n", hex.Dump(contentBytes))
+
+	file, err := box.Open("file.txt")
+	if err != nil {
+		log.Fatalf("could not open file: %s\n", err)
+	}
+	spew.Dump(file)
+
+	// find/create a rice.Box
+	templateBox, err := rice.FindBox("example-templates")
+	if err != nil {
+		log.Fatal(err)
+	}
+	// get file contents as string
+	templateString, err := templateBox.String("message.tmpl")
+	if err != nil {
+		log.Fatal(err)
+	}
+	// parse and execute the template
+	tmplMessage, err := template.New("message").Parse(templateString)
+	if err != nil {
+		log.Fatal(err)
+	}
+	tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
+
+	http.Handle("/", http.FileServer(box.HTTPBox()))
+	go func() {
+		fmt.Println("Serving files on :8080, press ctrl-C to exit")
+		err := http.ListenAndServe(":8080", nil)
+		if err != nil {
+			log.Fatalf("error serving files: %v", err)
+		}
+	}()
+	select {}
+}

+ 171 - 0
spirit/common/src/github.com/GeertJohan/go.rice/file.go

@@ -0,0 +1,171 @@
+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)
+}
+
+// Readdirnames is like (*os.File).Readdirnames()
+// Visit http://golang.org/pkg/os/#File.Readdirnames for more information
+func (f *File) Readdirnames(count int) ([]string, error) {
+	if f.appendedF != nil {
+		if f.appendedF.dir {
+			names := make([]string, 0, len(f.appendedF.children))
+			for _, childAppendedFile := range f.appendedF.children {
+				if childAppendedFile.dir {
+					names = append(names, childAppendedFile.dirInfo.name)
+				} else {
+					names = append(names, childAppendedFile.zipFile.FileInfo().Name())
+				}
+			}
+			return names, nil
+		}
+		// os.ErrInvalid to match the os.SyscallError (readdirent: invalid argument) that os.File returns
+		return nil, os.ErrInvalid
+	}
+	if f.virtualF != nil {
+		return f.virtualF.readdirnames(count)
+	}
+	if f.virtualD != nil {
+		return f.virtualD.readdirnames(count)
+	}
+	return f.realF.Readdirnames(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)
+}

+ 21 - 0
spirit/common/src/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)
+}

+ 157 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/append.go

@@ -0,0 +1,157 @@
+package main
+
+import (
+	"archive/zip"
+	"fmt"
+	"go/build"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	zipexe "github.com/daaku/go.zipexe"
+)
+
+func operationAppend(pkgs []*build.Package) {
+	// create tmp zipfile
+	tmpZipfileName := filepath.Join(os.TempDir(), fmt.Sprintf("ricebox-%d-%s.zip", time.Now().Unix(), randomString(10)))
+	verbosef("Will create tmp zipfile: %s\n", tmpZipfileName)
+	tmpZipfile, err := os.Create(tmpZipfileName)
+	if err != nil {
+		fmt.Printf("Error creating tmp zipfile: %s\n", err)
+		os.Exit(1)
+	}
+	defer func() {
+		tmpZipfile.Close()
+		os.Remove(tmpZipfileName)
+	}()
+
+	// find abs path for binary file
+	binfileName, err := filepath.Abs(flags.Append.Executable)
+	if err != nil {
+		fmt.Printf("Error finding absolute path for executable to append: %s\n", err)
+		os.Exit(1)
+	}
+	verbosef("Will append to file: %s\n", binfileName)
+
+	// check that command doesn't already have zip appended
+	if rd, _ := zipexe.Open(binfileName); rd != nil {
+		fmt.Printf("Cannot append to already appended executable. Please remove %s and build a fresh one.\n", binfileName)
+		os.Exit(1)
+	}
+
+	// open binfile
+	binfile, err := os.OpenFile(binfileName, os.O_WRONLY, os.ModeAppend)
+	if err != nil {
+		fmt.Printf("Error: unable to open executable file: %s\n", err)
+		os.Exit(1)
+	}
+	defer binfile.Close()
+
+	binfileInfo, err := binfile.Stat()
+	if err != nil {
+		fmt.Printf("Error: unable to stat executable file: %s\n", err)
+		os.Exit(1)
+	}
+
+	// create zip.Writer
+	zipWriter := zip.NewWriter(tmpZipfile)
+
+	// write the zip offset into the zip data
+	zipWriter.SetOffset(binfileInfo.Size())
+
+	for _, pkg := range pkgs {
+		// find boxes for this command
+		boxMap := findBoxes(pkg)
+
+		// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
+		if len(boxMap) == 0 {
+			fmt.Printf("no calls to rice.FindBox() or rice.MustFindBox() found in import path `%s`\n", pkg.ImportPath)
+			continue
+		}
+
+		verbosef("\n")
+
+		for boxname := range boxMap {
+			appendedBoxName := strings.Replace(boxname, `/`, `-`, -1)
+
+			// walk box path's and insert files
+			boxPath := filepath.Clean(filepath.Join(pkg.Dir, boxname))
+			filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
+				if info == nil {
+					fmt.Printf("Error: box \"%s\" not found on disk\n", path)
+					os.Exit(1)
+				}
+				// create zipFilename
+				zipFileName := filepath.Join(appendedBoxName, strings.TrimPrefix(path, boxPath))
+				// write directories as empty file with comment "dir"
+				if info.IsDir() {
+					_, err := zipWriter.CreateHeader(&zip.FileHeader{
+						Name:    zipFileName,
+						Comment: "dir",
+					})
+					if err != nil {
+						fmt.Printf("Error creating dir in tmp zip: %s\n", err)
+						os.Exit(1)
+					}
+					return nil
+				}
+
+				// create zipFileWriter
+				zipFileHeader, err := zip.FileInfoHeader(info)
+				if err != nil {
+					fmt.Printf("Error creating zip FileHeader: %v\n", err)
+					os.Exit(1)
+				}
+				zipFileHeader.Name = zipFileName
+				zipFileWriter, err := zipWriter.CreateHeader(zipFileHeader)
+				if err != nil {
+					fmt.Printf("Error creating file in tmp zip: %s\n", err)
+					os.Exit(1)
+				}
+				srcFile, err := os.Open(path)
+				if err != nil {
+					fmt.Printf("Error opening file to append: %s\n", err)
+					os.Exit(1)
+				}
+				_, err = io.Copy(zipFileWriter, srcFile)
+				if err != nil {
+					fmt.Printf("Error copying file contents to zip: %s\n", err)
+					os.Exit(1)
+				}
+				srcFile.Close()
+
+				return nil
+			})
+		}
+	}
+
+	err = zipWriter.Close()
+	if err != nil {
+		fmt.Printf("Error closing tmp zipfile: %s\n", err)
+		os.Exit(1)
+	}
+
+	err = tmpZipfile.Sync()
+	if err != nil {
+		fmt.Printf("Error syncing tmp zipfile: %s\n", err)
+		os.Exit(1)
+	}
+	_, err = tmpZipfile.Seek(0, 0)
+	if err != nil {
+		fmt.Printf("Error seeking tmp zipfile: %s\n", err)
+		os.Exit(1)
+	}
+	_, err = binfile.Seek(0, 2)
+	if err != nil {
+		fmt.Printf("Error seeking bin file: %s\n", err)
+		os.Exit(1)
+	}
+
+	_, err = io.Copy(binfile, tmpZipfile)
+	if err != nil {
+		fmt.Printf("Error appending zipfile to executable: %s\n", err)
+		os.Exit(1)
+	}
+}

+ 33 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/clean.go

@@ -0,0 +1,33 @@
+package main
+
+import (
+	"fmt"
+	"go/build"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+func operationClean(pkg *build.Package) {
+	filepath.Walk(pkg.Dir, func(filename string, info os.FileInfo, err error) error {
+		if err != nil {
+			fmt.Printf("error walking pkg dir to clean files: %v\n", err)
+			os.Exit(1)
+		}
+		if info.IsDir() {
+			return nil
+		}
+		verbosef("checking file '%s'\n", filename)
+		if filepath.Base(filename) == "rice-box.go" ||
+			strings.HasSuffix(filename, ".rice-box.go") ||
+			strings.HasSuffix(filename, ".rice-box.syso") {
+			err := os.Remove(filename)
+			if err != nil {
+				fmt.Printf("error removing file (%s): %s\n", filename, err)
+				os.Exit(-1)
+			}
+			verbosef("removed file '%s'\n", filename)
+		}
+		return nil
+	})
+}

+ 181 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/embed-go.go

@@ -0,0 +1,181 @@
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"errors"
+	"fmt"
+	"go/build"
+	"go/format"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+const boxFilename = "rice-box.go"
+
+// errEmptyBox is returned by writeBoxesGo when no calls to rice.FindBox
+// are found in the package.
+var errEmptyBox = errors.New("no calls to rice.FindBox() found")
+
+func writeBoxesGo(pkg *build.Package, out io.Writer) error {
+	boxMap := findBoxes(pkg)
+
+	if len(boxMap) == 0 {
+		return errEmptyBox
+	}
+
+	verbosef("\n")
+
+	var boxes []*boxDataType
+
+	for boxname := range boxMap {
+		// find path and filename for this box
+		boxPath := filepath.Join(pkg.Dir, boxname)
+
+		// Check to see if the path for the box is a symbolic link.  If so, simply
+		// box what the symbolic link points to.  Note: the filepath.Walk function
+		// will NOT follow any nested symbolic links.  This only handles the case
+		// where the root of the box is a symbolic link.
+		symPath, serr := os.Readlink(boxPath)
+		if serr == nil {
+			boxPath = symPath
+		}
+
+		// verbose info
+		verbosef("embedding box '%s' to '%s'\n", boxname, boxFilename)
+
+		// read box metadata
+		boxInfo, ierr := os.Stat(boxPath)
+		if ierr != nil {
+			return fmt.Errorf("unable to access box at %s", boxPath)
+		}
+
+		// create box datastructure (used by template)
+		box := &boxDataType{
+			BoxName: boxname,
+			UnixNow: boxInfo.ModTime().Unix(),
+			Files:   make([]*fileDataType, 0),
+			Dirs:    make(map[string]*dirDataType),
+		}
+
+		if !boxInfo.IsDir() {
+			return fmt.Errorf("box %s must point to a directory but points to %s instead",
+				boxname, boxPath)
+		}
+
+		// fill box datastructure with file data
+		err := filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				return fmt.Errorf("error walking box: %s", err)
+			}
+
+			filename := strings.TrimPrefix(path, boxPath)
+			filename = strings.Replace(filename, "\\", "/", -1)
+			filename = strings.TrimPrefix(filename, "/")
+			if info.IsDir() {
+				dirData := &dirDataType{
+					Identifier: "dir" + nextIdentifier(),
+					FileName:   filename,
+					ModTime:    info.ModTime().Unix(),
+					ChildFiles: make([]*fileDataType, 0),
+					ChildDirs:  make([]*dirDataType, 0),
+				}
+				verbosef("\tincludes dir: '%s'\n", dirData.FileName)
+				box.Dirs[dirData.FileName] = dirData
+
+				// add tree entry (skip for root, it'll create a recursion)
+				if dirData.FileName != "" {
+					pathParts := strings.Split(dirData.FileName, "/")
+					parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
+					parentDir.ChildDirs = append(parentDir.ChildDirs, dirData)
+				}
+			} else {
+				fileData := &fileDataType{
+					Identifier: "file" + nextIdentifier(),
+					FileName:   filename,
+					ModTime:    info.ModTime().Unix(),
+				}
+				verbosef("\tincludes file: '%s'\n", fileData.FileName)
+
+				// Instead of injecting content, inject placeholder for fasttemplate.
+				// This allows us to stream the content into the final file,
+				// and it also avoids running gofmt on a very large source code.
+				fileData.Path = path
+				box.Files = append(box.Files, fileData)
+
+				// add tree entry
+				pathParts := strings.Split(fileData.FileName, "/")
+				parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
+				if parentDir == nil {
+					return fmt.Errorf("parent of %s is not within the box", path)
+				}
+				parentDir.ChildFiles = append(parentDir.ChildFiles, fileData)
+			}
+			return nil
+		})
+		if err != nil {
+			return fmt.Errorf("failed in filepath walk: %v", err)
+		}
+		boxes = append(boxes, box)
+
+	}
+
+	embedSourceUnformated := bytes.NewBuffer(make([]byte, 0))
+
+	// execute template to buffer
+	err := tmplEmbeddedBox.Execute(
+		embedSourceUnformated,
+		embedFileDataType{pkg.Name, boxes},
+	)
+	if err != nil {
+		return fmt.Errorf("error writing embedded box to file (template execute): %s", err)
+	}
+
+	// format the source code
+	embedSource, err := format.Source(embedSourceUnformated.Bytes())
+	if err != nil {
+		return fmt.Errorf("error formatting embedSource: %s", err)
+	}
+
+	// write source to file
+	bufWriter := bufio.NewWriterSize(out, 100*1024)
+	err = embeddedBoxFasttemplate(bufWriter, string(embedSource))
+	if err != nil {
+		return fmt.Errorf("error writing embedSource to file: %s\n", err)
+	}
+	err = bufWriter.Flush()
+	if err != nil {
+		return fmt.Errorf("error writing embedSource to file: %s", err)
+	}
+	return nil
+}
+
+func operationEmbedGo(pkg *build.Package) {
+	// create go file for box
+	boxFile, err := os.Create(filepath.Join(pkg.Dir, boxFilename))
+	if err != nil {
+		log.Printf("error creating embedded box file: %s\n", err)
+		os.Exit(1)
+	}
+
+	err = writeBoxesGo(pkg, boxFile)
+	boxFile.Close()
+	if err != nil {
+		// don't leave an invalid go file in the package directory.
+		if errRemove := os.Remove(boxFile.Name()); errRemove != nil {
+			log.Printf("error while removing file: %s\n", errRemove)
+		}
+		if err != errEmptyBox {
+			log.Printf("error creating embedded box file: %s\n", err)
+			os.Exit(1)
+		} else {
+			// notify user when no calls to rice.FindBox are made,
+			// but don't fail, since it's useful to be able to run
+			// go.rice unconditionally.
+			log.Println(errEmptyBox)
+		}
+	}
+}

+ 134 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/embed-go_test.go

@@ -0,0 +1,134 @@
+package main
+
+import (
+	"bytes"
+	"path/filepath"
+	"testing"
+)
+
+func TestEmbedGo(t *testing.T) {
+	sourceFiles := []sourceFile{
+		{
+			"boxes.go",
+			[]byte(`package main
+
+import (
+	"github.com/GeertJohan/go.rice"
+)
+
+func main() {
+	rice.MustFindBox("foo")
+}
+`),
+		},
+		{
+			"foo/test1.txt",
+			[]byte(`This is test 1`),
+		},
+		{
+			"foo/test2.txt",
+			[]byte(`This is test 2`),
+		},
+		{
+			"foo/bar/test1.txt",
+			[]byte(`This is test 1 in bar`),
+		},
+		{
+			"foo/bar/baz/test1.txt",
+			[]byte(`This is test 1 in bar/baz`),
+		},
+		{
+			"foo/bar/baz/backtick`.txt",
+			[]byte(`Backtick filename`),
+		},
+		{
+			"foo/bar/baz/\"quote\".txt",
+			[]byte(`double quoted filename`),
+		},
+		{
+			"foo/bar/baz/'quote'.txt",
+			[]byte(`single quoted filename`),
+		},
+		{
+			"foo/`/`/`.txt",
+			[]byte(`Backticks everywhere!`),
+		},
+		{
+			"foo/new\nline",
+			[]byte("File with newline in name. Yes, this is possible."),
+		},
+		{
+			"foo/fast{%template%}",
+			[]byte("Fasttemplate"),
+		},
+		{
+			"foo/fast{%template",
+			[]byte("Fasttemplate open"),
+		},
+		{
+			"foo/fast%}template",
+			[]byte("Fasttemplate close"),
+		},
+		{
+			"foo/fast{%dir%}/test.txt",
+			[]byte("Fasttemplate directory"),
+		},
+		{
+			"foo/fast{%dir/test.txt",
+			[]byte("Fasttemplate directory open"),
+		},
+		{
+			"foo/fast%}dir/test.txt",
+			[]byte("Fasttemplate directory close"),
+		},
+		{
+			"foo/fast{$%template%$}",
+			[]byte("Fasttemplate double escaping"),
+		},
+	}
+	pkg, cleanup, err := setUpTestPkg("foobar", sourceFiles)
+	defer cleanup()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	var buffer bytes.Buffer
+
+	err = writeBoxesGo(pkg, &buffer)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	t.Logf("Generated file: \n%s", buffer.String())
+
+	validateBoxFile(t, filepath.Join(pkg.Dir, "rice-box.go"), &buffer, sourceFiles)
+}
+
+func TestEmbedGoEmpty(t *testing.T) {
+	sourceFiles := []sourceFile{
+		{
+			"boxes.go",
+			[]byte(`package main
+
+func main() {
+}
+`),
+		},
+	}
+	pkg, cleanup, err := setUpTestPkg("foobar", sourceFiles)
+	defer cleanup()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	var buffer bytes.Buffer
+
+	err = writeBoxesGo(pkg, &buffer)
+	if err != errEmptyBox {
+		t.Errorf("expected errEmptyBox, got %v", err)
+		return
+	}
+}

+ 204 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/embed-syso.go

@@ -0,0 +1,204 @@
+package main
+
+import (
+	"bytes"
+	"encoding/gob"
+	"fmt"
+	"go/build"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"text/template"
+
+	"github.com/GeertJohan/go.rice/embedded"
+	"github.com/akavel/rsrc/coff"
+)
+
+type sizedReader struct {
+	*bytes.Reader
+}
+
+func (s sizedReader) Size() int64 {
+	return int64(s.Len())
+}
+
+var tmplEmbeddedSysoHelper *template.Template
+
+func init() {
+	var err error
+	tmplEmbeddedSysoHelper, err = template.New("embeddedSysoHelper").Parse(`package {{.Package}}
+// ############# GENERATED CODE #####################
+// ## This file was generated by the rice tool.
+// ## Do not edit unless you know what you're doing.
+// ##################################################
+
+// extern char _bricebox_{{.Symname}}[], _ericebox_{{.Symname}};
+// int get_{{.Symname}}_length() {
+// 	return &_ericebox_{{.Symname}} - _bricebox_{{.Symname}};
+// }
+import "C"
+import (
+	"bytes"
+	"encoding/gob"
+	"github.com/GeertJohan/go.rice/embedded"
+	"unsafe"
+)
+
+func init() {
+	ptr := unsafe.Pointer(&C._bricebox_{{.Symname}})
+	bts := C.GoBytes(ptr, C.get_{{.Symname}}_length())
+	embeddedBox := &embedded.EmbeddedBox{}
+	err := gob.NewDecoder(bytes.NewReader(bts)).Decode(embeddedBox)
+	if err != nil {
+		panic("error decoding embedded box: "+err.Error())
+	}
+	embeddedBox.Link()
+	embedded.RegisterEmbeddedBox(embeddedBox.Name, embeddedBox)
+}`)
+	if err != nil {
+		panic("could not parse template embeddedSysoHelper: " + err.Error())
+	}
+}
+
+type embeddedSysoHelperData struct {
+	Package string
+	Symname string
+}
+
+func operationEmbedSyso(pkg *build.Package) {
+
+	regexpSynameReplacer := regexp.MustCompile(`[^a-z0-9_]`)
+
+	boxMap := findBoxes(pkg)
+
+	// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
+	if len(boxMap) == 0 {
+		fmt.Println("no calls to rice.FindBox() found")
+		return
+	}
+
+	verbosef("\n")
+
+	for boxname := range boxMap {
+		// find path and filename for this box
+		boxPath := filepath.Join(pkg.Dir, boxname)
+		boxFilename := strings.Replace(boxname, "/", "-", -1)
+		boxFilename = strings.Replace(boxFilename, "..", "back", -1)
+		boxFilename = strings.Replace(boxFilename, ".", "-", -1)
+
+		// verbose info
+		verbosef("embedding box '%s'\n", boxname)
+		verbosef("\tto file %s\n", boxFilename)
+
+		// read box metadata
+		boxInfo, ierr := os.Stat(boxPath)
+		if ierr != nil {
+			fmt.Printf("Error: unable to access box at %s\n", boxPath)
+			os.Exit(1)
+		}
+
+		// create box datastructure (used by template)
+		box := &embedded.EmbeddedBox{
+			Name:      boxname,
+			Time:      boxInfo.ModTime(),
+			EmbedType: embedded.EmbedTypeSyso,
+			Files:     make(map[string]*embedded.EmbeddedFile),
+			Dirs:      make(map[string]*embedded.EmbeddedDir),
+		}
+
+		// fill box datastructure with file data
+		filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				fmt.Printf("error walking box: %s\n", err)
+				os.Exit(1)
+			}
+
+			filename := strings.TrimPrefix(path, boxPath)
+			filename = strings.Replace(filename, "\\", "/", -1)
+			filename = strings.TrimPrefix(filename, "/")
+			if info.IsDir() {
+				embeddedDir := &embedded.EmbeddedDir{
+					Filename:   filename,
+					DirModTime: info.ModTime(),
+				}
+				verbosef("\tincludes dir: '%s'\n", embeddedDir.Filename)
+				box.Dirs[embeddedDir.Filename] = embeddedDir
+
+				// add tree entry (skip for root, it'll create a recursion)
+				if embeddedDir.Filename != "" {
+					pathParts := strings.Split(embeddedDir.Filename, "/")
+					parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
+					parentDir.ChildDirs = append(parentDir.ChildDirs, embeddedDir)
+				}
+			} else {
+				embeddedFile := &embedded.EmbeddedFile{
+					Filename:    filename,
+					FileModTime: info.ModTime(),
+					Content:     "",
+				}
+				verbosef("\tincludes file: '%s'\n", embeddedFile.Filename)
+				contentBytes, err := ioutil.ReadFile(path)
+				if err != nil {
+					fmt.Printf("error reading file content while walking box: %s\n", err)
+					os.Exit(1)
+				}
+				embeddedFile.Content = string(contentBytes)
+				box.Files[embeddedFile.Filename] = embeddedFile
+			}
+			return nil
+		})
+
+		// encode embedded box to gob file
+		boxGobBuf := &bytes.Buffer{}
+		err := gob.NewEncoder(boxGobBuf).Encode(box)
+		if err != nil {
+			fmt.Printf("error encoding box to gob: %v\n", err)
+			os.Exit(1)
+		}
+
+		verbosef("gob-encoded embeddedBox is %d bytes large\n", boxGobBuf.Len())
+
+		// write coff
+		symname := regexpSynameReplacer.ReplaceAllString(boxname, "_")
+		createCoffSyso(boxname, symname, "386", boxGobBuf.Bytes())
+		createCoffSyso(boxname, symname, "amd64", boxGobBuf.Bytes())
+
+		// write go
+		sysoHelperData := embeddedSysoHelperData{
+			Package: pkg.Name,
+			Symname: symname,
+		}
+		fileSysoHelper, err := os.Create(boxFilename + ".rice-box.go")
+		if err != nil {
+			fmt.Printf("error creating syso helper: %v\n", err)
+			os.Exit(1)
+		}
+		err = tmplEmbeddedSysoHelper.Execute(fileSysoHelper, sysoHelperData)
+		if err != nil {
+			fmt.Printf("error executing tmplEmbeddedSysoHelper: %v\n", err)
+			os.Exit(1)
+		}
+	}
+}
+
+func createCoffSyso(boxFilename string, symname string, arch string, data []byte) {
+	boxCoff := coff.NewRDATA()
+	switch arch {
+	case "386":
+	case "amd64":
+		boxCoff.FileHeader.Machine = 0x8664
+	default:
+		panic("invalid arch")
+	}
+	boxCoff.AddData("_bricebox_"+symname, sizedReader{bytes.NewReader(data)})
+	boxCoff.AddData("_ericebox_"+symname, io.NewSectionReader(strings.NewReader("\000\000"), 0, 2)) // TODO: why? copied from rsrc, which copied it from as-generated
+	boxCoff.Freeze()
+	err := writeCoff(boxCoff, boxFilename+"_"+arch+".rice-box.syso")
+	if err != nil {
+		fmt.Printf("error writing %s coff/.syso: %v\n", arch, err)
+		os.Exit(1)
+	}
+}

+ 148 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/find.go

@@ -0,0 +1,148 @@
+package main
+
+import (
+	"fmt"
+	"go/ast"
+	"go/build"
+	"go/parser"
+	"go/token"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+func badArgument(fileset *token.FileSet, p token.Pos) {
+	pos := fileset.Position(p)
+	filename := pos.Filename
+	base, err := os.Getwd()
+	if err == nil {
+		rpath, perr := filepath.Rel(base, pos.Filename)
+		if perr == nil {
+			filename = rpath
+		}
+	}
+	msg := fmt.Sprintf("%s:%d: Error: found call to rice.FindBox, "+
+		"but argument must be a string literal.\n", filename, pos.Line)
+	fmt.Println(msg)
+	os.Exit(1)
+}
+
+func findBoxes(pkg *build.Package) map[string]bool {
+	// create map of boxes to embed
+	var boxMap = make(map[string]bool)
+
+	// create one list of files for this package
+	filenames := make([]string, 0, len(pkg.GoFiles)+len(pkg.CgoFiles))
+	filenames = append(filenames, pkg.GoFiles...)
+	filenames = append(filenames, pkg.CgoFiles...)
+
+	// loop over files, search for rice.FindBox(..) calls
+	for _, filename := range filenames {
+		// find full filepath
+		fullpath := filepath.Join(pkg.Dir, filename)
+		if strings.HasSuffix(filename, "rice-box.go") {
+			// Ignore *.rice-box.go files
+			verbosef("skipping file %q\n", fullpath)
+			continue
+		}
+		verbosef("scanning file %q\n", fullpath)
+
+		fset := token.NewFileSet()
+		f, err := parser.ParseFile(fset, fullpath, nil, 0)
+		if err != nil {
+			fmt.Println(err)
+			os.Exit(1)
+		}
+
+		var riceIsImported bool
+		ricePkgName := "rice"
+		for _, imp := range f.Imports {
+			if strings.HasSuffix(imp.Path.Value, "go.rice\"") {
+				if imp.Name != nil {
+					ricePkgName = imp.Name.Name
+				}
+				riceIsImported = true
+				break
+			}
+		}
+		if !riceIsImported {
+			// Rice wasn't imported, so we won't find a box.
+			continue
+		}
+		if ricePkgName == "_" {
+			// Rice pkg is unnamed, so we won't find a box.
+			continue
+		}
+
+		// Inspect AST, looking for calls to (Must)?FindBox.
+		// First parameter of the func must be a basic literal.
+		// Identifiers won't be resolved.
+		var nextIdentIsBoxFunc bool
+		var nextBasicLitParamIsBoxName bool
+		var boxCall token.Pos
+		var validVariablesForBoxes = make(map[string]bool)
+
+		ast.Inspect(f, func(node ast.Node) bool {
+			if node == nil {
+				return false
+			}
+			switch x := node.(type) {
+			// this case fixes the var := func() style assignments, not assignments to vars declared separately from the assignment.
+			case *ast.AssignStmt:
+				var assign = node.(*ast.AssignStmt)
+				name, found := assign.Lhs[0].(*ast.Ident)
+				if found {
+					composite, first := assign.Rhs[0].(*ast.CompositeLit)
+					if first {
+						riceSelector, second := composite.Type.(*ast.SelectorExpr)
+
+						if second {
+							callCorrect := riceSelector.Sel.Name == "Config"
+							packageName, third := riceSelector.X.(*ast.Ident)
+
+							if third && callCorrect && packageName.Name == ricePkgName {
+								validVariablesForBoxes[name.Name] = true
+								verbosef("\tfound variable, saving to scan for boxes: %q\n", name.Name)
+							}
+						}
+					}
+				}
+			case *ast.Ident:
+				if nextIdentIsBoxFunc || ricePkgName == "." {
+					nextIdentIsBoxFunc = false
+					if x.Name == "FindBox" || x.Name == "MustFindBox" {
+						nextBasicLitParamIsBoxName = true
+						boxCall = x.Pos()
+					}
+				} else {
+					if x.Name == ricePkgName || validVariablesForBoxes[x.Name] {
+						nextIdentIsBoxFunc = true
+					}
+				}
+			case *ast.BasicLit:
+				if nextBasicLitParamIsBoxName {
+					if x.Kind == token.STRING {
+						nextBasicLitParamIsBoxName = false
+						// trim "" or ``
+						name := x.Value[1 : len(x.Value)-1]
+						boxMap[name] = true
+						verbosef("\tfound box %q\n", name)
+					} else {
+						badArgument(fset, boxCall)
+					}
+				}
+
+			default:
+				if nextIdentIsBoxFunc {
+					nextIdentIsBoxFunc = false
+				}
+				if nextBasicLitParamIsBoxName {
+					badArgument(fset, boxCall)
+				}
+			}
+			return true
+		})
+	}
+
+	return boxMap
+}

+ 268 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/find_test.go

@@ -0,0 +1,268 @@
+package main
+
+import (
+	"fmt"
+	"testing"
+)
+
+func expectBoxes(expected []string, actual map[string]bool) error {
+	if len(expected) != len(actual) {
+		return fmt.Errorf("expected %v, got %v", expected, actual)
+	}
+	for _, box := range expected {
+		if _, ok := actual[box]; !ok {
+			return fmt.Errorf("expected %v, got %v", expected, actual)
+		}
+	}
+	return nil
+}
+
+func TestFindOneBox(t *testing.T) {
+	pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
+		{
+			"boxes.go",
+			[]byte(`package main
+
+import (
+	"github.com/GeertJohan/go.rice"
+)
+
+func main() {
+	rice.MustFindBox("foo")
+}
+`),
+		},
+	})
+	defer cleanup()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	expectedBoxes := []string{"foo"}
+	boxMap := findBoxes(pkg)
+	if err := expectBoxes(expectedBoxes, boxMap); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestFindOneBoxViaVariable(t *testing.T) {
+
+	pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
+		{
+			"boxes.go",
+			[]byte(`package main
+
+import (
+	"github.com/GeertJohan/go.rice"
+)
+
+func main() {
+	conf := rice.Config{
+		LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS},
+	}
+	conf.MustFindBox("foo")
+}
+`),
+		},
+	})
+	defer cleanup()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	expectedBoxes := []string{"foo"}
+	boxMap := findBoxes(pkg)
+	if err := expectBoxes(expectedBoxes, boxMap); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestFindMultipleBoxes(t *testing.T) {
+	pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
+		{
+			"boxes.go",
+			[]byte(`package main
+
+import (
+	"github.com/GeertJohan/go.rice"
+)
+
+func main() {
+	rice.MustFindBox("foo")
+	rice.MustFindBox("bar")
+}
+`),
+		},
+	})
+	defer cleanup()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	expectedBoxes := []string{"foo", "bar"}
+	boxMap := findBoxes(pkg)
+	if err := expectBoxes(expectedBoxes, boxMap); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestNoBoxFoundIfRiceNotImported(t *testing.T) {
+	pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
+		{
+			"boxes.go",
+			[]byte(`package main
+type fakerice struct {}
+
+func (fr fakerice) FindBox(s string) {
+}
+
+func main() {
+	rice := fakerice{}
+	rice.FindBox("foo")
+}
+`),
+		},
+	})
+	defer cleanup()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	boxMap := findBoxes(pkg)
+	if _, ok := boxMap["foo"]; ok {
+		t.Errorf("Unexpected box %q was found", "foo")
+	}
+}
+
+func TestUnrelatedBoxesAreNotFound(t *testing.T) {
+	pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
+		{
+			"boxes.go",
+			[]byte(`package foobar
+
+import (
+	_ "github.com/GeertJohan/go.rice"
+)
+
+type fakerice struct {}
+
+func (fr fakerice) FindBox(s string) {
+}
+
+func FindBox(s string) {
+
+}
+
+func LoadBoxes() {
+	rice := fakerice{}
+	rice.FindBox("foo")
+
+	FindBox("bar")
+}
+`),
+		},
+	})
+	defer cleanup()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	boxMap := findBoxes(pkg)
+	for _, box := range []string{"foo", "bar"} {
+		if _, ok := boxMap[box]; ok {
+			t.Errorf("Unexpected box %q was found", box)
+		}
+	}
+}
+
+func TestMixGoodAndBadBoxes(t *testing.T) {
+	pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
+		{
+			"boxes1.go",
+			[]byte(`package foobar
+
+import (
+	_ "github.com/GeertJohan/go.rice"
+)
+
+type fakerice struct {}
+
+func (fr fakerice) FindBox(s string) {
+}
+
+func FindBox(s string) {
+
+}
+
+func LoadBoxes1() {
+	rice := fakerice{}
+	rice.FindBox("foo")
+
+	FindBox("bar")
+}
+`),
+		},
+		{
+			"boxes2.go",
+			[]byte(`package foobar
+
+import (
+	noodles "github.com/GeertJohan/go.rice"
+)
+
+func LoadBoxes2() {
+	FindBox("baz")
+	noodles.FindBox("veggies")
+}
+`),
+		},
+		{
+			"boxes3.go",
+			[]byte(`package foobar
+
+import (
+	"github.com/GeertJohan/go.rice"
+)
+
+func LoadBoxes3() {
+	rice.FindBox("fish")
+}
+`),
+		},
+		{
+			"boxes4.go",
+			[]byte(`package foobar
+
+import (
+	. "github.com/GeertJohan/go.rice"
+)
+
+func LoadBoxes3() {
+	MustFindBox("chicken")
+}
+`),
+		},
+	})
+	defer cleanup()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	boxMap := findBoxes(pkg)
+	for _, box := range []string{"foo", "bar", "baz"} {
+		if _, ok := boxMap[box]; ok {
+			t.Errorf("Unexpected box %q was found", box)
+		}
+	}
+	for _, box := range []string{"veggies", "fish", "chicken"} {
+		if _, ok := boxMap[box]; !ok {
+			t.Errorf("Expected box %q not found", box)
+		}
+	}
+}

+ 82 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/flags.go

@@ -0,0 +1,82 @@
+package main
+
+import (
+	"fmt"
+	"go/build"
+	"os"
+
+	goflags "github.com/jessevdk/go-flags" // rename import to `goflags` (file scope) so we can use `var flags` (package scope)
+)
+
+// flags
+var flags struct {
+	MemProfile  string   `long:"memprofile" description:"Write memory profile to this file"`
+	CpuProfile  string   `long:"cpuprofile" description:"Write cpu profile to this file"`
+	Verbose     bool     `long:"verbose" short:"v" description:"Show verbose debug information"`
+	ImportPaths []string `long:"import-path" short:"i" description:"Import path(s) to use. Using PWD when left empty. Specify multiple times for more import paths to append"`
+
+	Append struct {
+		Executable string `long:"exec" description:"Executable to append" required:"true"`
+	} `command:"append"`
+
+	EmbedGo   struct{} `command:"embed-go" alias:"embed"`
+	EmbedSyso struct{} `command:"embed-syso"`
+	Clean     struct{} `command:"clean"`
+}
+
+// flags parser
+var flagsParser *goflags.Parser
+
+// initFlags parses the given flags.
+// when the user asks for help (-h or --help): the application exists with status 0
+// when unexpected flags is given: the application exits with status 1
+func parseArguments() {
+	// create flags parser in global var, for flagsParser.Active.Name (operation)
+	flagsParser = goflags.NewParser(&flags, goflags.Default)
+
+	// parse flags
+	args, err := flagsParser.Parse()
+	if err != nil {
+		// assert the err to be a flags.Error
+		flagError := err.(*goflags.Error)
+		if flagError.Type == goflags.ErrHelp {
+			// user asked for help on flags.
+			// program can exit successfully
+			os.Exit(0)
+		}
+		if flagError.Type == goflags.ErrUnknownFlag {
+			fmt.Println("Use --help to view available options.")
+			os.Exit(1)
+		}
+		if flagError.Type == goflags.ErrRequired {
+			os.Exit(1)
+		}
+		fmt.Printf("Error parsing flags: %s\n", err)
+		os.Exit(1)
+	}
+
+	// error on left-over arguments
+	if len(args) > 0 {
+		fmt.Printf("Unexpected arguments: %s\nUse --help to view available options.", args)
+		os.Exit(1)
+	}
+
+	// default ImportPath to pwd when not set
+	if len(flags.ImportPaths) == 0 {
+		pwd, err := os.Getwd()
+		if err != nil {
+			fmt.Printf("error getting pwd: %s\n", err)
+			os.Exit(1)
+		}
+		verbosef("using pwd as import path\n")
+		// find non-absolute path for this pwd
+		pkg, err := build.ImportDir(pwd, build.FindOnly)
+		if err != nil {
+			fmt.Printf("error using current directory as import path: %s\n", err)
+			os.Exit(1)
+		}
+		flags.ImportPaths = append(flags.ImportPaths, pkg.ImportPath)
+		verbosef("using import paths: %s\n", flags.ImportPaths)
+		return
+	}
+}

+ 646 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/helpers_test.go

@@ -0,0 +1,646 @@
+package main
+
+import (
+	"fmt"
+	"go/ast"
+	"go/build"
+	"go/parser"
+	"go/token"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"testing"
+)
+
+type sourceFile struct {
+	Name     string
+	Contents []byte
+}
+
+type registeredDir struct {
+	Filename   string
+	ModTime    int
+	ChildFiles []*registeredFile
+	ChildDirs  []*registeredDir
+}
+
+type registeredFile struct {
+	Filename string
+	ModTime  int
+	Content  string
+}
+
+type registeredBox struct {
+	Name string
+	Time int
+	// key is path
+	Dirs map[string]*registeredDir
+	// key is path
+	Files map[string]*registeredFile
+}
+
+func setUpTestPkg(pkgName string, files []sourceFile) (*build.Package, func(), error) {
+	temp, err := ioutil.TempDir("", "go.rice-test")
+	if err != nil {
+		return nil, func() {}, err
+	}
+	cleanup := func() {
+		os.RemoveAll(temp)
+	}
+	dir := filepath.Join(temp, pkgName)
+	if err := os.Mkdir(dir, 0770); err != nil {
+		return nil, cleanup, err
+	}
+	for _, f := range files {
+		fullPath := filepath.Join(dir, f.Name)
+		if err := os.MkdirAll(filepath.Dir(fullPath), 0770); err != nil {
+			return nil, cleanup, err
+		}
+		if err := ioutil.WriteFile(fullPath, f.Contents, 0660); err != nil {
+			return nil, cleanup, err
+		}
+	}
+	pkg, err := build.ImportDir(dir, 0)
+	return pkg, cleanup, err
+}
+
+// isSimpleSelector returns true if expr is pkgName.ident
+func isSimpleSelector(pkgName, ident string, expr ast.Expr) bool {
+	if sel, ok := expr.(*ast.SelectorExpr); ok {
+		if pkgIdent, ok := sel.X.(*ast.Ident); ok && pkgIdent.Name == pkgName && sel.Sel != nil && sel.Sel.Name == ident {
+			return true
+		}
+	}
+	return false
+}
+
+func isIdent(ident string, expr ast.Expr) bool {
+	if expr, ok := expr.(*ast.Ident); ok && expr.Name == ident {
+		return true
+	}
+	return false
+}
+
+func getIdentName(expr ast.Expr) (string, bool) {
+	if expr, ok := expr.(*ast.Ident); ok {
+		return expr.Name, true
+	}
+	return "", false
+}
+
+func getKey(expr *ast.KeyValueExpr) string {
+	if ident, ok := expr.Key.(*ast.Ident); ok {
+		return ident.Name
+	}
+	return ""
+}
+
+// parseModTime parses a time.Unix call, and returns the unix time.
+func parseModTime(expr ast.Expr) (int, error) {
+	if expr, ok := expr.(*ast.CallExpr); ok {
+		if !isSimpleSelector("time", "Unix", expr.Fun) {
+			return 0, fmt.Errorf("ModTime is not time.Unix: %#v", expr.Fun)
+		}
+		if len(expr.Args) == 0 {
+			return 0, fmt.Errorf("not enough args to time.Unix")
+		}
+		arg0 := expr.Args[0]
+		if lit, ok := arg0.(*ast.BasicLit); ok && lit.Kind == token.INT {
+			return strconv.Atoi(lit.Value)
+		}
+	}
+	return 0, fmt.Errorf("not time.Unix: %#v", expr)
+}
+
+func parseString(expr ast.Expr) (string, error) {
+	if expr, ok := expr.(*ast.CallExpr); ok && isIdent("string", expr.Fun) && len(expr.Args) == 1 {
+		return parseString(expr.Args[0])
+	}
+	if lit, ok := expr.(*ast.BasicLit); ok && lit.Kind == token.STRING {
+		return strconv.Unquote(lit.Value)
+	}
+	return "", fmt.Errorf("not string: %#v", expr)
+}
+
+// parseDir parses an embedded.EmbeddedDir literal.
+// It can be either a variable name or a composite literal.
+// Returns nil if the literal is not embedded.EmbeddedDir.
+func parseDir(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (*registeredDir, []error) {
+
+	if varName, ok := getIdentName(expr); ok {
+		dir, ok := dirs[varName]
+		if !ok {
+			return nil, []error{fmt.Errorf("unknown variable %v", varName)}
+		}
+		return dir, nil
+	}
+
+	lit, ok := expr.(*ast.CompositeLit)
+	if !ok {
+		return nil, []error{fmt.Errorf("dir is not a composite literal: %#v", expr)}
+	}
+
+	var errors []error
+	if !isSimpleSelector("embedded", "EmbeddedDir", lit.Type) {
+		return nil, nil
+	}
+	ret := &registeredDir{}
+	for _, el := range lit.Elts {
+		if el, ok := el.(*ast.KeyValueExpr); ok {
+			key := getKey(el)
+			if key == "" {
+				continue
+			}
+			switch key {
+			case "DirModTime":
+				var err error
+				ret.ModTime, err = parseModTime(el.Value)
+				if err != nil {
+					errors = append(errors, fmt.Errorf("DirModTime %s", err))
+				}
+			case "Filename":
+				var err error
+				ret.Filename, err = parseString(el.Value)
+				if err != nil {
+					errors = append(errors, fmt.Errorf("Filename %s", err))
+				}
+			case "ChildDirs":
+				var errors2 []error
+				ret.ChildDirs, errors2 = parseDirsSlice(el.Value, dirs, files)
+				errors = append(errors, errors2...)
+			case "ChildFiles":
+				var errors2 []error
+				ret.ChildFiles, errors2 = parseFilesSlice(el.Value, files)
+				errors = append(errors, errors2...)
+			default:
+				errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
+			}
+		}
+	}
+	return ret, errors
+}
+
+// parseFile parses an embedded.EmbeddedFile literal.
+// It can be either a variable name or a composite literal.
+// Returns nil if the literal is not embedded.EmbeddedFile.
+func parseFile(expr ast.Expr, files map[string]*registeredFile) (*registeredFile, []error) {
+	if varName, ok := getIdentName(expr); ok {
+		file, ok := files[varName]
+		if !ok {
+			return nil, []error{fmt.Errorf("unknown variable %v", varName)}
+		}
+		return file, nil
+	}
+
+	lit, ok := expr.(*ast.CompositeLit)
+	if !ok {
+		return nil, []error{fmt.Errorf("file is not a composite literal: %#v", expr)}
+	}
+
+	var errors []error
+	if !isSimpleSelector("embedded", "EmbeddedFile", lit.Type) {
+		return nil, nil
+	}
+	ret := &registeredFile{}
+	for _, el := range lit.Elts {
+		if el, ok := el.(*ast.KeyValueExpr); ok {
+			key := getKey(el)
+			if key == "" {
+				continue
+			}
+			switch key {
+			case "FileModTime":
+				var err error
+				ret.ModTime, err = parseModTime(el.Value)
+				if err != nil {
+					errors = append(errors, fmt.Errorf("DirModTime %s", err))
+				}
+			case "Filename":
+				var err error
+				ret.Filename, err = parseString(el.Value)
+				if err != nil {
+					errors = append(errors, fmt.Errorf("Filename %s", err))
+				}
+			case "Content":
+				var err error
+				ret.Content, err = parseString(el.Value)
+				if err != nil {
+					errors = append(errors, fmt.Errorf("Content %s", err))
+				}
+			default:
+				errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
+			}
+		}
+	}
+	return ret, errors
+}
+
+func parseRegistration(lit *ast.CompositeLit, dirs map[string]*registeredDir, files map[string]*registeredFile) (*registeredBox, []error) {
+	var errors []error
+	if !isSimpleSelector("embedded", "EmbeddedBox", lit.Type) {
+		return nil, nil
+	}
+	ret := &registeredBox{
+		Dirs:  make(map[string]*registeredDir),
+		Files: make(map[string]*registeredFile),
+	}
+	for _, el := range lit.Elts {
+		if el, ok := el.(*ast.KeyValueExpr); ok {
+			key := getKey(el)
+			if key == "" {
+				continue
+			}
+			switch key {
+			case "Time":
+				var err error
+				ret.Time, err = parseModTime(el.Value)
+				if err != nil {
+					errors = append(errors, fmt.Errorf("Time %s", err))
+				}
+			case "Name":
+				var err error
+				ret.Name, err = parseString(el.Value)
+				if err != nil {
+					errors = append(errors, fmt.Errorf("Name %s", err))
+				}
+			case "Dirs":
+				var errors2 []error
+				ret.Dirs, errors2 = parseDirsMap(el.Value, dirs, files)
+				errors = append(errors, errors2...)
+			case "Files":
+				var errors2 []error
+				ret.Files, errors2 = parseFilesMap(el.Value, files)
+				errors = append(errors, errors2...)
+			default:
+				errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
+			}
+		}
+	}
+	return ret, errors
+}
+
+func parseDirsSlice(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (childDirs []*registeredDir, errors []error) {
+	valid := false
+	lit, ok := expr.(*ast.CompositeLit)
+	if ok {
+		if arrType, ok := lit.Type.(*ast.ArrayType); ok {
+			if star, ok := arrType.Elt.(*ast.StarExpr); ok {
+				if isSimpleSelector("embedded", "EmbeddedDir", star.X) {
+					valid = true
+				}
+			}
+		}
+	}
+
+	if !valid {
+		return nil, []error{fmt.Errorf("not a []*embedded.EmbeddedDir: %#v", expr)}
+	}
+	for _, el := range lit.Elts {
+		child, childErrors := parseDir(el, dirs, files)
+		errors = append(errors, childErrors...)
+		childDirs = append(childDirs, child)
+	}
+	return
+}
+
+func parseFilesSlice(expr ast.Expr, files map[string]*registeredFile) (childFiles []*registeredFile, errors []error) {
+	valid := false
+	lit, ok := expr.(*ast.CompositeLit)
+	if ok {
+		if arrType, ok := lit.Type.(*ast.ArrayType); ok {
+			if star, ok := arrType.Elt.(*ast.StarExpr); ok {
+				if isSimpleSelector("embedded", "EmbeddedFile", star.X) {
+					valid = true
+				}
+			}
+		}
+	}
+
+	if !valid {
+		return nil, []error{fmt.Errorf("not a []*embedded.EmbeddedFile: %#v", expr)}
+	}
+	for _, el := range lit.Elts {
+		child, childErrors := parseFile(el, files)
+		errors = append(errors, childErrors...)
+		childFiles = append(childFiles, child)
+	}
+	return
+}
+
+func parseDirsMap(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (childDirs map[string]*registeredDir, errors []error) {
+	valid := false
+	lit, ok := expr.(*ast.CompositeLit)
+	if ok {
+		if mapType, ok := lit.Type.(*ast.MapType); ok {
+			if star, ok := mapType.Value.(*ast.StarExpr); ok {
+				if isSimpleSelector("embedded", "EmbeddedDir", star.X) && isIdent("string", mapType.Key) {
+					valid = true
+				}
+			}
+		}
+	}
+
+	if !valid {
+		return nil, []error{fmt.Errorf("not a map[string]*embedded.EmbeddedDir: %#v", expr)}
+	}
+	childDirs = make(map[string]*registeredDir)
+	for _, el := range lit.Elts {
+		kv, ok := el.(*ast.KeyValueExpr)
+		if !ok {
+			errors = append(errors, fmt.Errorf("not a KeyValueExpr: %#v", el))
+			continue
+		}
+		key, err := parseString(kv.Key)
+		if err != nil {
+			errors = append(errors, fmt.Errorf("key %s", err))
+			continue
+		}
+
+		child, childErrors := parseDir(kv.Value, dirs, files)
+		errors = append(errors, childErrors...)
+		childDirs[key] = child
+	}
+	return
+}
+
+func parseFilesMap(expr ast.Expr, files map[string]*registeredFile) (childFiles map[string]*registeredFile, errors []error) {
+	valid := false
+	lit, ok := expr.(*ast.CompositeLit)
+	if ok {
+		if mapType, ok := lit.Type.(*ast.MapType); ok {
+			if star, ok := mapType.Value.(*ast.StarExpr); ok {
+				if isSimpleSelector("embedded", "EmbeddedFile", star.X) && isIdent("string", mapType.Key) {
+					valid = true
+				}
+			}
+		}
+	}
+
+	if !valid {
+		return nil, []error{fmt.Errorf("not a map[string]*embedded.EmbeddedFile: %#v", expr)}
+	}
+	childFiles = make(map[string]*registeredFile)
+	for _, el := range lit.Elts {
+		kv, ok := el.(*ast.KeyValueExpr)
+		if !ok {
+			errors = append(errors, fmt.Errorf("not a KeyValueExpr: %#v", el))
+			continue
+		}
+		key, err := parseString(kv.Key)
+		if err != nil {
+			errors = append(errors, fmt.Errorf("key %s", err))
+			continue
+		}
+
+		child, childErrors := parseFile(kv.Value, files)
+		errors = append(errors, childErrors...)
+		childFiles[key] = child
+	}
+	return
+}
+
+// unpoint returns the expression expr points to
+// if expr is a & unary expression.
+func unpoint(expr ast.Expr) ast.Expr {
+	if expr, ok := expr.(*ast.UnaryExpr); ok {
+		if expr.Op == token.AND {
+			return expr.X
+		}
+	}
+	return expr
+}
+
+func validateBoxFile(t *testing.T, filename string, src io.Reader, sourceFiles []sourceFile) {
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, filename, src, 0)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	var initFunc *ast.FuncDecl
+	for _, decl := range f.Decls {
+		if decl, ok := decl.(*ast.FuncDecl); ok && decl.Name != nil && decl.Name.Name == "init" {
+			initFunc = decl
+			break
+		}
+	}
+	if initFunc == nil {
+		t.Fatal("init function not found in generated file")
+	}
+	if initFunc.Body == nil {
+		t.Fatal("init function has no body in generated file")
+	}
+	var registrations []*ast.CallExpr
+	directories := make(map[string]*registeredDir)
+	files := make(map[string]*registeredFile)
+	_ = directories
+	_ = files
+	for _, stmt := range initFunc.Body.List {
+		if stmt, ok := stmt.(*ast.ExprStmt); ok {
+			if call, ok := stmt.X.(*ast.CallExpr); ok {
+				registrations = append(registrations, call)
+			}
+			continue
+		}
+		if stmt, ok := stmt.(*ast.AssignStmt); ok {
+			for i, rhs := range stmt.Rhs {
+				// Rhs can be EmbeddedDir or EmbeddedFile.
+				var literal *ast.CompositeLit
+				literal, ok := unpoint(rhs).(*ast.CompositeLit)
+				if !ok {
+					continue
+				}
+				if lhs, ok := stmt.Lhs[i].(*ast.Ident); ok {
+					// variable
+					edir, direrrs := parseDir(literal, directories, files)
+					efile, fileerrs := parseFile(literal, files)
+					abort := false
+					for _, err := range direrrs {
+						t.Error("error while parsing dir: ", err)
+						abort = true
+					}
+					for _, err := range fileerrs {
+						t.Error("error while parsing file: ", err)
+						abort = true
+					}
+					if abort {
+						return
+					}
+
+					if edir == nil && efile == nil {
+						continue
+					}
+					if edir != nil {
+						directories[lhs.Name] = edir
+					} else {
+						files[lhs.Name] = efile
+					}
+				} else if lhs, ok := stmt.Lhs[i].(*ast.SelectorExpr); ok {
+					selName, ok := getIdentName(lhs.Sel)
+					if !ok || selName != "ChildDirs" {
+						continue
+					}
+					varName, ok := getIdentName(lhs.X)
+					if !ok {
+						t.Fatalf("cannot parse ChildDirs assignment: %#v", lhs)
+					}
+					dir, ok := directories[varName]
+					if !ok {
+						t.Fatalf("variable %v not found", varName)
+					}
+
+					var errors []error
+					dir.ChildDirs, errors = parseDirsSlice(rhs, directories, files)
+
+					abort := false
+					for _, err := range errors {
+						t.Errorf("error parsing child dirs: %s", err)
+						abort = true
+					}
+					if abort {
+						return
+					}
+				}
+			}
+		}
+	}
+	if len(registrations) == 0 {
+		t.Fatal("could not find registration of embedded box")
+	}
+
+	boxes := make(map[string]*registeredBox)
+
+	for _, call := range registrations {
+		if isSimpleSelector("embedded", "RegisterEmbeddedBox", call.Fun) {
+			if len(call.Args) != 2 {
+				t.Fatalf("incorrect arguments to embedded.RegisterEmbeddedBox: %#v", call.Args)
+			}
+			boxArg := unpoint(call.Args[1])
+			name, err := parseString(call.Args[0])
+			if err != nil {
+				t.Fatalf("first argument to embedded.RegisterEmbeddedBox incorrect: %s", err)
+			}
+			boxLit, ok := boxArg.(*ast.CompositeLit)
+			if !ok {
+				t.Fatalf("second argument to embedded.RegisterEmbeddedBox is not a composite literal: %#v", boxArg)
+			}
+			abort := false
+			box, errors := parseRegistration(boxLit, directories, files)
+			for _, err := range errors {
+				t.Error("error while parsing box: ", err)
+				abort = true
+			}
+			if abort {
+				return
+			}
+			if box == nil {
+				t.Fatalf("second argument to embedded.RegisterEmbeddedBox is not an embedded.EmbeddedBox: %#v", boxArg)
+			}
+			if box.Name != name {
+				t.Fatalf("first argument to embedded.RegisterEmbeddedBox is not the same as the name in the second argument: %v, %#v", name, boxArg)
+			}
+			boxes[name] = box
+		}
+	}
+
+	// Validate that all boxes are present.
+	if _, ok := boxes["foo"]; !ok {
+		t.Error("box \"foo\" not found")
+	}
+	for _, box := range boxes {
+		validateBox(t, box, sourceFiles)
+	}
+}
+
+func validateBox(t *testing.T, box *registeredBox, files []sourceFile) {
+	dirsToBeChecked := make(map[string]struct{})
+	filesToBeChecked := make(map[string]string)
+	for _, file := range files {
+		if !strings.HasPrefix(file.Name, box.Name) {
+			continue
+		}
+		pathParts := strings.Split(file.Name, "/")
+		dirs := pathParts[:len(pathParts)-1]
+		dirPath := ""
+		for _, dir := range dirs {
+			if dir != box.Name {
+				dirPath = path.Join(dirPath, dir)
+			}
+			dirsToBeChecked[dirPath] = struct{}{}
+		}
+		filesToBeChecked[path.Join(dirPath, pathParts[len(pathParts)-1])] = string(file.Contents)
+	}
+
+	if len(box.Files) != len(filesToBeChecked) {
+		t.Errorf("box %v has incorrect number of files; expected %v, got %v", box.Name, len(filesToBeChecked), len(box.Files))
+	}
+
+	if len(box.Dirs) != len(dirsToBeChecked) {
+		t.Errorf("box %v has incorrect number of dirs; expected %v, got %v", box.Name, len(dirsToBeChecked), len(box.Dirs))
+	}
+
+	for name, content := range filesToBeChecked {
+		f, ok := box.Files[name]
+		if !ok {
+			t.Errorf("file %v not present in box %v", name, box.Name)
+			continue
+		}
+		if f.Filename != name {
+			t.Errorf("box %v: filename mismatch: key: %v; Filename: %v", box.Name, name, f.Filename)
+		}
+		if f.Content != content {
+			t.Errorf("box %v: file %v content does not match: got %v, expected %v", box.Name, name, f.Content, content)
+		}
+		dirPath, _ := path.Split(name)
+		dirPath = strings.TrimSuffix(dirPath, "/")
+		dir, ok := box.Dirs[dirPath]
+		if !ok {
+			t.Errorf("directory %v not present in box %v", dirPath, box.Name)
+			continue
+		}
+		found := false
+		for _, file := range dir.ChildFiles {
+			if file == f {
+				found = true
+			}
+		}
+		if !found {
+			t.Errorf("file %v not found in directory %v in box %v", name, dirPath, box.Name)
+			continue
+		}
+	}
+	for name := range dirsToBeChecked {
+		d, ok := box.Dirs[name]
+		if !ok {
+			t.Errorf("directory %v not present in box %v", name, box.Name)
+			continue
+		}
+		if d.Filename != name {
+			t.Errorf("box %v: filename mismatch: key: %v; Filename: %v", box.Name, name, d.Filename)
+		}
+		if name != "" {
+			dirPath, _ := path.Split(name)
+			dirPath = strings.TrimSuffix(dirPath, "/")
+			dir, ok := box.Dirs[dirPath]
+			if !ok {
+				t.Errorf("directory %v not present in box %v", dirPath, box.Name)
+				continue
+			}
+			found := false
+			for _, dir := range dir.ChildDirs {
+				if dir == d {
+					found = true
+				}
+			}
+			if !found {
+				t.Errorf("directory %v not found in directory %v in box %v", name, dirPath, box.Name)
+				continue
+			}
+		}
+	}
+}

+ 14 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/identifier.go

@@ -0,0 +1,14 @@
+package main
+
+import (
+	"strconv"
+
+	"github.com/GeertJohan/go.incremental"
+)
+
+var identifierCount incremental.Uint64
+
+func nextIdentifier() string {
+	num := identifierCount.Next()
+	return strconv.FormatUint(num, 36) // 0123456789abcdefghijklmnopqrstuvwxyz
+}

+ 87 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/main.go

@@ -0,0 +1,87 @@
+package main
+
+import (
+	"fmt"
+	"go/build"
+	"log"
+	"os"
+	"runtime/pprof"
+)
+
+func main() {
+	// parser arguments
+	parseArguments()
+
+	if flags.CpuProfile != "" {
+		f, err := os.Create(flags.CpuProfile)
+		if err != nil {
+			log.Fatal(err)
+		}
+		pprof.StartCPUProfile(f)
+		defer pprof.StopCPUProfile()
+	}
+
+	// find package for path
+	var pkgs []*build.Package
+	for _, importPath := range flags.ImportPaths {
+		pkg := pkgForPath(importPath)
+		pkgs = append(pkgs, pkg)
+	}
+
+	// switch on the operation to perform
+	switch flagsParser.Active.Name {
+	case "embed", "embed-go":
+		for _, pkg := range pkgs {
+			operationEmbedGo(pkg)
+		}
+	case "embed-syso":
+		log.Println("WARNING: embedding .syso is experimental..")
+		for _, pkg := range pkgs {
+			operationEmbedSyso(pkg)
+		}
+	case "append":
+		operationAppend(pkgs)
+	case "clean":
+		for _, pkg := range pkgs {
+			operationClean(pkg)
+		}
+	}
+
+	// all done
+	verbosef("\n")
+	verbosef("rice finished successfully\n")
+
+	if flags.MemProfile != "" {
+		f, err := os.Create(flags.MemProfile)
+		if err != nil {
+			log.Fatal(err)
+		}
+		pprof.WriteHeapProfile(f)
+		f.Close()
+	}
+}
+
+// helper function to get *build.Package for given path
+func pkgForPath(path string) *build.Package {
+	// get pwd for relative imports
+	pwd, err := os.Getwd()
+	if err != nil {
+		fmt.Printf("error getting pwd (required for relative imports): %s\n", err)
+		os.Exit(1)
+	}
+
+	// read full package information
+	pkg, err := build.Import(path, pwd, 0)
+	if err != nil {
+		fmt.Printf("error reading package: %s\n", err)
+		os.Exit(1)
+	}
+
+	return pkg
+}
+
+func verbosef(format string, stuff ...interface{}) {
+	if flags.Verbose {
+		log.Printf(format, stuff...)
+	}
+}

+ 177 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/templates.go

@@ -0,0 +1,177 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+	"text/template"
+
+	"github.com/nkovacs/streamquote"
+	"github.com/valyala/fasttemplate"
+)
+
+var (
+	tmplEmbeddedBox          *template.Template
+	tagEscaper, tagUnescaper *strings.Replacer
+)
+
+const (
+	unescapeTag = "unescape:"
+	injectTag   = "injectfile:"
+)
+
+func init() {
+	var err error
+
+	// $ is used as the escaping character,
+	// because it has no special meaning in go strings,
+	// so it won't be changed by strconv.Quote.
+	replacements := []string{"$", "$$", "{%", "{$%", "%}", "%$}"}
+	reverseReplacements := make([]string, len(replacements))
+	l := len(reverseReplacements) - 1
+	for i := range replacements {
+		reverseReplacements[l-i] = replacements[i]
+	}
+	tagEscaper = strings.NewReplacer(replacements...)
+	tagUnescaper = strings.NewReplacer(reverseReplacements...)
+
+	// parse embedded box template
+	tmplEmbeddedBox, err = template.New("embeddedBox").Funcs(template.FuncMap{
+		"tagescape": func(s string) string {
+			return fmt.Sprintf("{%%%v%v%%}", unescapeTag, tagEscaper.Replace(s))
+		},
+		"injectfile": func(s string) string {
+			return fmt.Sprintf("{%%%v%v%%}", injectTag, tagEscaper.Replace(s))
+		},
+	}).Parse(`package {{.Package}}
+
+import (
+	"time"
+
+	"github.com/GeertJohan/go.rice/embedded"
+)
+
+{{range .Boxes}}
+func init() {
+
+	// define files
+	{{range .Files}}{{.Identifier}} := &embedded.EmbeddedFile{
+		Filename:    {{.FileName | tagescape | printf "%q"}},
+		FileModTime: time.Unix({{.ModTime}}, 0),
+
+		Content:     string({{.Path | injectfile | printf "%q"}}),
+	}
+	{{end}}
+
+	// define dirs
+	{{range .Dirs}}{{.Identifier}} := &embedded.EmbeddedDir{
+		Filename:    {{.FileName | tagescape | printf "%q"}},
+		DirModTime: time.Unix({{.ModTime}}, 0),
+		ChildFiles:  []*embedded.EmbeddedFile{
+			{{range .ChildFiles}}{{.Identifier}}, // {{.FileName | tagescape | printf "%q"}}
+			{{end}}
+		},
+	}
+	{{end}}
+
+	// link ChildDirs
+	{{range .Dirs}}{{.Identifier}}.ChildDirs = []*embedded.EmbeddedDir{
+		{{range .ChildDirs}}{{.Identifier}}, // {{.FileName | tagescape | printf "%q"}}
+		{{end}}
+	}
+	{{end}}
+
+	// register embeddedBox
+	embedded.RegisterEmbeddedBox(` + "`" + `{{.BoxName}}` + "`" + `, &embedded.EmbeddedBox{
+		Name: ` + "`" + `{{.BoxName}}` + "`" + `,
+		Time: time.Unix({{.UnixNow}}, 0),
+		Dirs: map[string]*embedded.EmbeddedDir{
+			{{range .Dirs}}{{.FileName | tagescape | printf "%q"}}: {{.Identifier}},
+			{{end}}
+		},
+		Files: map[string]*embedded.EmbeddedFile{
+			{{range .Files}}{{.FileName | tagescape | printf "%q"}}: {{.Identifier}},
+			{{end}}
+		},
+	})
+}
+{{end}}`)
+	if err != nil {
+		fmt.Printf("error parsing embedded box template: %s\n", err)
+		os.Exit(-1)
+	}
+}
+
+// embeddedBoxFasttemplate will inject file contents and unescape {% and %}.
+func embeddedBoxFasttemplate(w io.Writer, src string) error {
+	ft, err := fasttemplate.NewTemplate(src, "{%", "%}")
+	if err != nil {
+		return fmt.Errorf("error compiling fasttemplate: %s\n", err)
+	}
+
+	converter := streamquote.New()
+
+	_, err = ft.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) {
+		if strings.HasPrefix(tag, unescapeTag) {
+			tag = strings.TrimPrefix(tag, unescapeTag)
+			return w.Write([]byte(tagUnescaper.Replace(tag)))
+		}
+		if !strings.HasPrefix(tag, injectTag) {
+			return 0, fmt.Errorf("invalid fasttemplate tag: %v", tag)
+		}
+		tag = strings.TrimPrefix(tag, injectTag)
+
+		fileName, err := strconv.Unquote("\"" + tag + "\"")
+		if err != nil {
+			return 0, fmt.Errorf("error unquoting filename %v: %v\n", tag, err)
+		}
+		f, err := os.Open(tagUnescaper.Replace(fileName))
+		if err != nil {
+			return 0, fmt.Errorf("error opening file %v: %v\n", fileName, err)
+		}
+
+		n, err := converter.Convert(f, w)
+
+		f.Close()
+		if err != nil {
+			return n, fmt.Errorf("error converting file %v: %v\n", fileName, err)
+		}
+
+		return n, nil
+	})
+	if err != nil {
+		return fmt.Errorf("error executing fasttemplate: %s\n", err)
+	}
+
+	return nil
+}
+
+type embedFileDataType struct {
+	Package string
+	Boxes   []*boxDataType
+}
+
+type boxDataType struct {
+	BoxName string
+	UnixNow int64
+	Files   []*fileDataType
+	Dirs    map[string]*dirDataType
+}
+
+type fileDataType struct {
+	Identifier string
+	FileName   string
+	Path       string
+	ModTime    int64
+}
+
+type dirDataType struct {
+	Identifier string
+	FileName   string
+	Content    []byte
+	ModTime    int64
+	ChildDirs  []*dirDataType
+	ChildFiles []*fileDataType
+}

+ 22 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/util.go

@@ -0,0 +1,22 @@
+package main
+
+import (
+	"math/rand"
+	"time"
+)
+
+// randomString generates a pseudo-random alpha-numeric string with given length.
+func randomString(length int) string {
+	rand.Seed(time.Now().UnixNano())
+	k := make([]rune, length)
+	for i := 0; i < length; i++ {
+		c := rand.Intn(35)
+		if c < 10 {
+			c += 48 // numbers (0-9) (0+48 == 48 == '0', 9+48 == 57 == '9')
+		} else {
+			c += 87 // lower case alphabets (a-z) (10+87 == 97 == 'a', 35+87 == 122 = 'z')
+		}
+		k[i] = rune(c)
+	}
+	return string(k)
+}

+ 42 - 0
spirit/common/src/github.com/GeertJohan/go.rice/rice/writecoff.go

@@ -0,0 +1,42 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+
+	"github.com/akavel/rsrc/binutil"
+	"github.com/akavel/rsrc/coff"
+)
+
+// copied from github.com/akavel/rsrc
+// LICENSE: MIT
+// Copyright 2013-2014 The rsrc Authors. (https://github.com/akavel/rsrc/blob/master/AUTHORS)
+func writeCoff(coff *coff.Coff, fnameout string) error {
+	out, err := os.Create(fnameout)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+	w := binutil.Writer{W: out}
+
+	// write the resulting file to disk
+	binutil.Walk(coff, func(v reflect.Value, path string) error {
+		if binutil.Plain(v.Kind()) {
+			w.WriteLE(v.Interface())
+			return nil
+		}
+		vv, ok := v.Interface().(binutil.SizedReader)
+		if ok {
+			w.WriteFromSized(vv)
+			return binutil.WALK_SKIP
+		}
+		return nil
+	})
+
+	if w.Err != nil {
+		return fmt.Errorf("Error writing output file: %s", w.Err)
+	}
+
+	return nil
+}

+ 19 - 0
spirit/common/src/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] }

+ 304 - 0
spirit/common/src/github.com/GeertJohan/go.rice/virtual.go

@@ -0,0 +1,304 @@
+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
+
+// 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"),
+		}
+	}
+	return nil, os.ErrInvalid
+}
+
+func (vf *virtualFile) readdirnames(count int) ([]string, error) {
+	if vf.closed {
+		return nil, &os.PathError{
+			Op:   "readdirnames",
+			Path: vf.EmbeddedFile.Filename,
+			Err:  errors.New("bad file descriptor"),
+		}
+	}
+	return nil, os.ErrInvalid
+}
+
+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) ([]os.FileInfo, 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) readdirnames(n int) ([]string, 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 []string
+
+	// Add the child directories
+	for _, child := range vd.ChildDirs {
+		files = append(files, filepath.Base(child.Filename))
+	}
+
+	// Add the child files
+	for _, child := range vd.ChildFiles {
+		files = append(files, filepath.Base(child.Filename))
+	}
+
+	// Sort it by filename (lexical order)
+	sort.Strings(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"),
+	}
+}

+ 122 - 0
spirit/common/src/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
+
+}

+ 1 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/.gitattributes

@@ -0,0 +1 @@
+testdata/* linguist-vendored

+ 16 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/.gitignore

@@ -0,0 +1,16 @@
+# editor temporary files
+*.sublime-*
+.DS_Store
+*.swp
+#*.*#
+tags
+
+# direnv config
+.env*
+
+# test binaries
+*.test
+
+# coverage and profilte outputs
+*.out
+

+ 17 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/.travis.yml

@@ -0,0 +1,17 @@
+language: go
+
+go:
+    - 1.2.x
+    - 1.3.x
+    - 1.4.x
+    - 1.5.x
+    - 1.6.x
+    - 1.7.x
+    - 1.8.x
+    - 1.9.x
+    - 1.10.x
+    - 1.11.x
+    - 1.12.x
+    - 1.13.x
+    - tip
+

+ 12 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/LICENSE

@@ -0,0 +1,12 @@
+Copyright (c) 2012-2016, Martin Angers & Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 181 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/README.md

@@ -0,0 +1,181 @@
+# goquery - a little like that j-thing, only in Go
+[![build status](https://secure.travis-ci.org/PuerkitoBio/goquery.svg?branch=master)](http://travis-ci.org/PuerkitoBio/goquery) [![GoDoc](https://godoc.org/github.com/PuerkitoBio/goquery?status.png)](http://godoc.org/github.com/PuerkitoBio/goquery) [![Sourcegraph Badge](https://sourcegraph.com/github.com/PuerkitoBio/goquery/-/badge.svg)](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge)
+
+goquery brings a syntax and a set of features similar to [jQuery][] to the [Go language][go]. It is based on Go's [net/html package][html] and the CSS Selector library [cascadia][]. Since the net/html parser returns nodes, and not a full-featured DOM tree, jQuery's stateful manipulation functions (like height(), css(), detach()) have been left off.
+
+Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML. See the [wiki][] for various options to do this.
+
+Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...).
+
+## Table of Contents
+
+* [Installation](#installation)
+* [Changelog](#changelog)
+* [API](#api)
+* [Examples](#examples)
+* [Related Projects](#related-projects)
+* [Support](#support)
+* [License](#license)
+
+## Installation
+
+Please note that because of the net/html dependency, goquery requires Go1.1+.
+
+    $ go get github.com/PuerkitoBio/goquery
+
+(optional) To run unit tests:
+
+    $ cd $GOPATH/src/github.com/PuerkitoBio/goquery
+    $ go test
+
+(optional) To run benchmarks (warning: it runs for a few minutes):
+
+    $ cd $GOPATH/src/github.com/PuerkitoBio/goquery
+    $ go test -bench=".*"
+
+## Changelog
+
+**Note that goquery's API is now stable, and will not break.**
+
+*    **2018-11-15 (v1.5.0)** : Go module support (thanks @Zaba505).
+*    **2018-06-07 (v1.4.1)** : Add `NewDocumentFromReader` examples.
+*    **2018-03-24 (v1.4.0)** : Deprecate `NewDocument(url)` and `NewDocumentFromResponse(response)`.
+*    **2018-01-28 (v1.3.0)** : Add `ToEnd` constant to `Slice` until the end of the selection (thanks to @davidjwilkins for raising the issue).
+*    **2018-01-11 (v1.2.0)** : Add `AddBack*` and deprecate `AndSelf` (thanks to @davidjwilkins).
+*    **2017-02-12 (v1.1.0)** : Add `SetHtml` and `SetText` (thanks to @glebtv).
+*    **2016-12-29 (v1.0.2)** : Optimize allocations for `Selection.Text` (thanks to @radovskyb).
+*    **2016-08-28 (v1.0.1)** : Optimize performance for large documents.
+*    **2016-07-27 (v1.0.0)** : Tag version 1.0.0.
+*    **2016-06-15** : Invalid selector strings internally compile to a `Matcher` implementation that never matches any node (instead of a panic). So for example, `doc.Find("~")` returns an empty `*Selection` object.
+*    **2016-02-02** : Add `NodeName` utility function similar to the DOM's `nodeName` property. It returns the tag name of the first element in a selection, and other relevant values of non-element nodes (see godoc for details). Add `OuterHtml` utility function similar to the DOM's `outerHTML` property (named `OuterHtml` in small caps for consistency with the existing `Html` method on the `Selection`).
+*    **2015-04-20** : Add `AttrOr` helper method to return the attribute's value or a default value if absent. Thanks to [piotrkowalczuk][piotr].
+*    **2015-02-04** : Add more manipulation functions - Prepend* - thanks again to [Andrew Stone][thatguystone].
+*    **2014-11-28** : Add more manipulation functions - ReplaceWith*, Wrap* and Unwrap - thanks again to [Andrew Stone][thatguystone].
+*    **2014-11-07** : Add manipulation functions (thanks to [Andrew Stone][thatguystone]) and `*Matcher` functions, that receive compiled cascadia selectors instead of selector strings, thus avoiding potential panics thrown by goquery via `cascadia.MustCompile` calls. This results in better performance (selectors can be compiled once and reused) and more idiomatic error handling (you can handle cascadia's compilation errors, instead of recovering from panics, which had been bugging me for a long time). Note that the actual type expected is a `Matcher` interface, that `cascadia.Selector` implements. Other matcher implementations could be used.
+*    **2014-11-06** : Change import paths of net/html to golang.org/x/net/html (see https://groups.google.com/forum/#!topic/golang-nuts/eD8dh3T9yyA). Make sure to update your code to use the new import path too when you call goquery with `html.Node`s.
+*    **v0.3.2** : Add `NewDocumentFromReader()` (thanks jweir) which allows creating a goquery document from an io.Reader.
+*    **v0.3.1** : Add `NewDocumentFromResponse()` (thanks assassingj) which allows creating a goquery document from an http response.
+*    **v0.3.0** : Add `EachWithBreak()` which allows to break out of an `Each()` loop by returning false. This function was added instead of changing the existing `Each()` to avoid breaking compatibility.
+*    **v0.2.1** : Make go-getable, now that [go.net/html is Go1.0-compatible][gonet] (thanks to @matrixik for pointing this out).
+*    **v0.2.0** : Add support for negative indices in Slice(). **BREAKING CHANGE** `Document.Root` is removed, `Document` is now a `Selection` itself (a selection of one, the root element, just like `Document.Root` was before). Add jQuery's Closest() method.
+*    **v0.1.1** : Add benchmarks to use as baseline for refactorings, refactor Next...() and Prev...() methods to use the new html package's linked list features (Next/PrevSibling, FirstChild). Good performance boost (40+% in some cases).
+*    **v0.1.0** : Initial release.
+
+## API
+
+goquery exposes two structs, `Document` and `Selection`, and the `Matcher` interface. Unlike jQuery, which is loaded as part of a DOM document, and thus acts on its containing document, goquery doesn't know which HTML document to act upon. So it needs to be told, and that's what the `Document` type is for. It holds the root document node as the initial Selection value to manipulate.
+
+jQuery often has many variants for the same function (no argument, a selector string argument, a jQuery object argument, a DOM element argument, ...). Instead of exposing the same features in goquery as a single method with variadic empty interface arguments, statically-typed signatures are used following this naming convention:
+
+*    When the jQuery equivalent can be called with no argument, it has the same name as jQuery for the no argument signature (e.g.: `Prev()`), and the version with a selector string argument is called `XxxFiltered()` (e.g.: `PrevFiltered()`)
+*    When the jQuery equivalent **requires** one argument, the same name as jQuery is used for the selector string version (e.g.: `Is()`)
+*    The signatures accepting a jQuery object as argument are defined in goquery as `XxxSelection()` and take a `*Selection` object as argument (e.g.: `FilterSelection()`)
+*    The signatures accepting a DOM element as argument in jQuery are defined in goquery as `XxxNodes()` and take a variadic argument of type `*html.Node` (e.g.: `FilterNodes()`)
+*    The signatures accepting a function as argument in jQuery are defined in goquery as `XxxFunction()` and take a function as argument (e.g.: `FilterFunction()`)
+*    The goquery methods that can be called with a selector string have a corresponding version that take a `Matcher` interface and are defined as `XxxMatcher()` (e.g.: `IsMatcher()`)
+
+Utility functions that are not in jQuery but are useful in Go are implemented as functions (that take a `*Selection` as parameter), to avoid a potential naming clash on the `*Selection`'s methods (reserved for jQuery-equivalent behaviour).
+
+The complete [godoc reference documentation can be found here][doc].
+
+Please note that Cascadia's selectors do not necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details. Invalid selector strings compile to a `Matcher` that fails to match any node. Behaviour of the various functions that take a selector string as argument follows from that fact, e.g. (where `~` is an invalid selector string):
+
+* `Find("~")` returns an empty selection because the selector string doesn't match anything.
+* `Add("~")` returns a new selection that holds the same nodes as the original selection, because it didn't add any node (selector string didn't match anything).
+* `ParentsFiltered("~")` returns an empty selection because the selector string doesn't match anything.
+* `ParentsUntil("~")` returns all parents of the selection because the selector string didn't match any element to stop before the top element.
+
+## Examples
+
+See some tips and tricks in the [wiki][].
+
+Adapted from example_test.go:
+
+```Go
+package main
+
+import (
+  "fmt"
+  "log"
+  "net/http"
+
+  "github.com/PuerkitoBio/goquery"
+)
+
+func ExampleScrape() {
+  // Request the HTML page.
+  res, err := http.Get("http://metalsucks.net")
+  if err != nil {
+    log.Fatal(err)
+  }
+  defer res.Body.Close()
+  if res.StatusCode != 200 {
+    log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
+  }
+
+  // Load the HTML document
+  doc, err := goquery.NewDocumentFromReader(res.Body)
+  if err != nil {
+    log.Fatal(err)
+  }
+
+  // Find the review items
+  doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) {
+    // For each item found, get the band and title
+    band := s.Find("a").Text()
+    title := s.Find("i").Text()
+    fmt.Printf("Review %d: %s - %s\n", i, band, title)
+  })
+}
+
+func main() {
+  ExampleScrape()
+}
+```
+
+## Related Projects
+
+- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags.
+- [andybalholm/cascadia][cascadia], the CSS selector library used by goquery.
+- [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors.
+- [gocolly/colly](https://github.com/gocolly/colly), a lightning fast and elegant Scraping Framework
+- [gnulnx/goperf](https://github.com/gnulnx/goperf), a website performance test tool that also fetches static assets.
+- [MontFerret/ferret](https://github.com/MontFerret/ferret), declarative web scraping.
+- [tacusci/berrycms](https://github.com/tacusci/berrycms), a modern simple to use CMS with easy to write plugins
+- [Dataflow kit](https://github.com/slotix/dataflowkit), Web Scraping framework for Gophers.  
+
+## Support
+
+There are a number of ways you can support the project:
+
+* Use it, star it, build something with it, spread the word!
+  - If you do build something open-source or otherwise publicly-visible, let me know so I can add it to the [Related Projects](#related-projects) section!
+* Raise issues to improve the project (note: doc typos and clarifications are issues too!)
+  - Please search existing issues before opening a new one - it may have already been adressed.
+* Pull requests: please discuss new code in an issue first, unless the fix is really trivial.
+  - Make sure new code is tested.
+  - Be mindful of existing code - PRs that break existing code have a high probability of being declined, unless it fixes a serious issue.
+
+If you desperately want to send money my way, I have a BuyMeACoffee.com page:
+
+<a href="https://www.buymeacoffee.com/mna" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
+
+## License
+
+The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic].
+
+[jquery]: http://jquery.com/
+[go]: http://golang.org/
+[cascadia]: https://github.com/andybalholm/cascadia
+[cascadiacli]: https://github.com/suntong/cascadia
+[bsd]: http://opensource.org/licenses/BSD-3-Clause
+[golic]: http://golang.org/LICENSE
+[caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE
+[doc]: http://godoc.org/github.com/PuerkitoBio/goquery
+[index]: http://api.jquery.com/index/
+[gonet]: https://github.com/golang/net/
+[html]: http://godoc.org/golang.org/x/net/html
+[wiki]: https://github.com/PuerkitoBio/goquery/wiki/Tips-and-tricks
+[thatguystone]: https://github.com/thatguystone
+[piotr]: https://github.com/piotrkowalczuk
+[goq]: https://github.com/andrewstuart/goq

+ 124 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/array.go

@@ -0,0 +1,124 @@
+package goquery
+
+import (
+	"golang.org/x/net/html"
+)
+
+const (
+	maxUint = ^uint(0)
+	maxInt  = int(maxUint >> 1)
+
+	// ToEnd is a special index value that can be used as end index in a call
+	// to Slice so that all elements are selected until the end of the Selection.
+	// It is equivalent to passing (*Selection).Length().
+	ToEnd = maxInt
+)
+
+// First reduces the set of matched elements to the first in the set.
+// It returns a new Selection object, and an empty Selection object if the
+// the selection is empty.
+func (s *Selection) First() *Selection {
+	return s.Eq(0)
+}
+
+// Last reduces the set of matched elements to the last in the set.
+// It returns a new Selection object, and an empty Selection object if
+// the selection is empty.
+func (s *Selection) Last() *Selection {
+	return s.Eq(-1)
+}
+
+// Eq reduces the set of matched elements to the one at the specified index.
+// If a negative index is given, it counts backwards starting at the end of the
+// set. It returns a new Selection object, and an empty Selection object if the
+// index is invalid.
+func (s *Selection) Eq(index int) *Selection {
+	if index < 0 {
+		index += len(s.Nodes)
+	}
+
+	if index >= len(s.Nodes) || index < 0 {
+		return newEmptySelection(s.document)
+	}
+
+	return s.Slice(index, index+1)
+}
+
+// Slice reduces the set of matched elements to a subset specified by a range
+// of indices. The start index is 0-based and indicates the index of the first
+// element to select. The end index is 0-based and indicates the index at which
+// the elements stop being selected (the end index is not selected).
+//
+// The indices may be negative, in which case they represent an offset from the
+// end of the selection.
+//
+// The special value ToEnd may be specified as end index, in which case all elements
+// until the end are selected. This works both for a positive and negative start
+// index.
+func (s *Selection) Slice(start, end int) *Selection {
+	if start < 0 {
+		start += len(s.Nodes)
+	}
+	if end == ToEnd {
+		end = len(s.Nodes)
+	} else if end < 0 {
+		end += len(s.Nodes)
+	}
+	return pushStack(s, s.Nodes[start:end])
+}
+
+// Get retrieves the underlying node at the specified index.
+// Get without parameter is not implemented, since the node array is available
+// on the Selection object.
+func (s *Selection) Get(index int) *html.Node {
+	if index < 0 {
+		index += len(s.Nodes) // Negative index gets from the end
+	}
+	return s.Nodes[index]
+}
+
+// Index returns the position of the first element within the Selection object
+// relative to its sibling elements.
+func (s *Selection) Index() int {
+	if len(s.Nodes) > 0 {
+		return newSingleSelection(s.Nodes[0], s.document).PrevAll().Length()
+	}
+	return -1
+}
+
+// IndexSelector returns the position of the first element within the
+// Selection object relative to the elements matched by the selector, or -1 if
+// not found.
+func (s *Selection) IndexSelector(selector string) int {
+	if len(s.Nodes) > 0 {
+		sel := s.document.Find(selector)
+		return indexInSlice(sel.Nodes, s.Nodes[0])
+	}
+	return -1
+}
+
+// IndexMatcher returns the position of the first element within the
+// Selection object relative to the elements matched by the matcher, or -1 if
+// not found.
+func (s *Selection) IndexMatcher(m Matcher) int {
+	if len(s.Nodes) > 0 {
+		sel := s.document.FindMatcher(m)
+		return indexInSlice(sel.Nodes, s.Nodes[0])
+	}
+	return -1
+}
+
+// IndexOfNode returns the position of the specified node within the Selection
+// object, or -1 if not found.
+func (s *Selection) IndexOfNode(node *html.Node) int {
+	return indexInSlice(s.Nodes, node)
+}
+
+// IndexOfSelection returns the position of the first node in the specified
+// Selection object within this Selection object, or -1 if not found.
+func (s *Selection) IndexOfSelection(sel *Selection) int {
+	if sel != nil && len(sel.Nodes) > 0 {
+		return indexInSlice(s.Nodes, sel.Nodes[0])
+	}
+	return -1
+}

+ 234 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/array_test.go

@@ -0,0 +1,234 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func TestFirst(t *testing.T) {
+	sel := Doc().Find(".pvk-content").First()
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestFirstEmpty(t *testing.T) {
+	sel := Doc().Find(".pvk-zzcontentzz").First()
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestFirstInvalid(t *testing.T) {
+	sel := Doc().Find("").First()
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestFirstRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.First().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestLast(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Last()
+	assertLength(t, sel.Nodes, 1)
+
+	// Should contain Footer
+	foot := Doc().Find(".footer")
+	if !sel.Contains(foot.Nodes[0]) {
+		t.Error("Last .pvk-content should contain .footer.")
+	}
+}
+
+func TestLastEmpty(t *testing.T) {
+	sel := Doc().Find(".pvk-zzcontentzz").Last()
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestLastInvalid(t *testing.T) {
+	sel := Doc().Find("").Last()
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestLastRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Last().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestEq(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Eq(1)
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestEqNegative(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Eq(-1)
+	assertLength(t, sel.Nodes, 1)
+
+	// Should contain Footer
+	foot := Doc().Find(".footer")
+	if !sel.Contains(foot.Nodes[0]) {
+		t.Error("Index -1 of .pvk-content should contain .footer.")
+	}
+}
+
+func TestEqEmpty(t *testing.T) {
+	sel := Doc().Find("something_random_that_does_not_exists").Eq(0)
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestEqInvalid(t *testing.T) {
+	sel := Doc().Find("").Eq(0)
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestEqInvalidPositive(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Eq(3)
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestEqInvalidNegative(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Eq(-4)
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestEqRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Eq(1).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestSlice(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Slice(0, 2)
+
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel, "#pc1", "#pc2")
+}
+
+func TestSliceToEnd(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Slice(1, ToEnd)
+
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel.Eq(0), "#pc2")
+	if _, ok := sel.Eq(1).Attr("id"); ok {
+		t.Error("Want no attribute ID, got one")
+	}
+}
+
+func TestSliceEmpty(t *testing.T) {
+	defer assertPanic(t)
+	Doc().Find("x").Slice(0, 2)
+}
+
+func TestSliceInvalid(t *testing.T) {
+	defer assertPanic(t)
+	Doc().Find("").Slice(0, 2)
+}
+
+func TestSliceInvalidToEnd(t *testing.T) {
+	defer assertPanic(t)
+	Doc().Find("").Slice(2, ToEnd)
+}
+
+func TestSliceOutOfBounds(t *testing.T) {
+	defer assertPanic(t)
+	Doc().Find(".pvk-content").Slice(2, 12)
+}
+
+func TestNegativeSliceStart(t *testing.T) {
+	sel := Doc().Find(".container-fluid").Slice(-2, 3)
+	assertLength(t, sel.Nodes, 1)
+	assertSelectionIs(t, sel.Eq(0), "#cf3")
+}
+
+func TestNegativeSliceEnd(t *testing.T) {
+	sel := Doc().Find(".container-fluid").Slice(1, -1)
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel.Eq(0), "#cf2")
+	assertSelectionIs(t, sel.Eq(1), "#cf3")
+}
+
+func TestNegativeSliceBoth(t *testing.T) {
+	sel := Doc().Find(".container-fluid").Slice(-3, -1)
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel.Eq(0), "#cf2")
+	assertSelectionIs(t, sel.Eq(1), "#cf3")
+}
+
+func TestNegativeSliceToEnd(t *testing.T) {
+	sel := Doc().Find(".container-fluid").Slice(-3, ToEnd)
+	assertLength(t, sel.Nodes, 3)
+	assertSelectionIs(t, sel, "#cf2", "#cf3", "#cf4")
+}
+
+func TestNegativeSliceOutOfBounds(t *testing.T) {
+	defer assertPanic(t)
+	Doc().Find(".container-fluid").Slice(-12, -7)
+}
+
+func TestSliceRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Slice(0, 2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestGet(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	node := sel.Get(1)
+	if sel.Nodes[1] != node {
+		t.Errorf("Expected node %v to be %v.", node, sel.Nodes[1])
+	}
+}
+
+func TestGetNegative(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	node := sel.Get(-3)
+	if sel.Nodes[0] != node {
+		t.Errorf("Expected node %v to be %v.", node, sel.Nodes[0])
+	}
+}
+
+func TestGetInvalid(t *testing.T) {
+	defer assertPanic(t)
+	sel := Doc().Find(".pvk-content")
+	sel.Get(129)
+}
+
+func TestIndex(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	if i := sel.Index(); i != 1 {
+		t.Errorf("Expected index of 1, got %v.", i)
+	}
+}
+
+func TestIndexSelector(t *testing.T) {
+	sel := Doc().Find(".hero-unit")
+	if i := sel.IndexSelector("div"); i != 4 {
+		t.Errorf("Expected index of 4, got %v.", i)
+	}
+}
+
+func TestIndexSelectorInvalid(t *testing.T) {
+	sel := Doc().Find(".hero-unit")
+	if i := sel.IndexSelector(""); i != -1 {
+		t.Errorf("Expected index of -1, got %v.", i)
+	}
+}
+
+func TestIndexOfNode(t *testing.T) {
+	sel := Doc().Find("div.pvk-gutter")
+	if i := sel.IndexOfNode(sel.Nodes[1]); i != 1 {
+		t.Errorf("Expected index of 1, got %v.", i)
+	}
+}
+
+func TestIndexOfNilNode(t *testing.T) {
+	sel := Doc().Find("div.pvk-gutter")
+	if i := sel.IndexOfNode(nil); i != -1 {
+		t.Errorf("Expected index of -1, got %v.", i)
+	}
+}
+
+func TestIndexOfSelection(t *testing.T) {
+	sel := Doc().Find("div")
+	sel2 := Doc().Find(".hero-unit")
+	if i := sel.IndexOfSelection(sel2); i != 4 {
+		t.Errorf("Expected index of 4, got %v.", i)
+	}
+}

+ 436 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.1.0

@@ -0,0 +1,436 @@
+PASS
+BenchmarkFirst	20000000	        92.9 ns/op
+BenchmarkLast	20000000	        91.6 ns/op
+BenchmarkEq	20000000	        90.6 ns/op
+BenchmarkSlice	20000000	        86.7 ns/op
+BenchmarkGet	1000000000	         2.14 ns/op
+BenchmarkIndex	  500000	      5308 ns/op
+--- BENCH: BenchmarkIndex
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+BenchmarkIndexSelector	   50000	     54962 ns/op
+--- BENCH: BenchmarkIndexSelector
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+BenchmarkIndexOfNode	100000000	        11.4 ns/op
+--- BENCH: BenchmarkIndexOfNode
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+BenchmarkIndexOfSelection	100000000	        12.1 ns/op
+--- BENCH: BenchmarkIndexOfSelection
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+BenchmarkMetalReviewExample	    5000	    327144 ns/op
+--- BENCH: BenchmarkMetalReviewExample
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+		
+	bench_example_test.go:41: MetalReviewExample=10
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+	... [output truncated]
+BenchmarkAdd	   50000	     52945 ns/op
+--- BENCH: BenchmarkAdd
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+BenchmarkAddSelection	10000000	       205 ns/op
+--- BENCH: BenchmarkAddSelection
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+BenchmarkAddNodes	10000000	       203 ns/op
+--- BENCH: BenchmarkAddNodes
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+BenchmarkAndSelf	 1000000	      2639 ns/op
+--- BENCH: BenchmarkAndSelf
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+BenchmarkFilter	   50000	     30182 ns/op
+--- BENCH: BenchmarkFilter
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+BenchmarkNot	   50000	     34855 ns/op
+--- BENCH: BenchmarkNot
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+BenchmarkFilterFunction	   50000	     66052 ns/op
+--- BENCH: BenchmarkFilterFunction
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+BenchmarkNotFunction	   50000	     69721 ns/op
+--- BENCH: BenchmarkNotFunction
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+BenchmarkFilterNodes	   50000	     66077 ns/op
+--- BENCH: BenchmarkFilterNodes
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+BenchmarkNotNodes	   20000	     80021 ns/op
+--- BENCH: BenchmarkNotNodes
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+BenchmarkFilterSelection	   50000	     66256 ns/op
+--- BENCH: BenchmarkFilterSelection
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+BenchmarkNotSelection	   20000	     79568 ns/op
+--- BENCH: BenchmarkNotSelection
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+BenchmarkHas	    5000	    569441 ns/op
+--- BENCH: BenchmarkHas
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+BenchmarkHasNodes	   10000	    230585 ns/op
+--- BENCH: BenchmarkHasNodes
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+BenchmarkHasSelection	   10000	    231470 ns/op
+--- BENCH: BenchmarkHasSelection
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+BenchmarkEnd	500000000	         4.65 ns/op
+--- BENCH: BenchmarkEnd
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+BenchmarkEach	  200000	      9558 ns/op
+--- BENCH: BenchmarkEach
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+BenchmarkMap	  100000	     16809 ns/op
+--- BENCH: BenchmarkMap
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+BenchmarkAttr	50000000	        37.5 ns/op
+--- BENCH: BenchmarkAttr
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+BenchmarkText	  100000	     18583 ns/op
+BenchmarkLength	2000000000	         0.80 ns/op
+--- BENCH: BenchmarkLength
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+BenchmarkHtml	 5000000	       666 ns/op
+BenchmarkIs	   50000	     34328 ns/op
+--- BENCH: BenchmarkIs
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+BenchmarkIsPositional	   50000	     32423 ns/op
+--- BENCH: BenchmarkIsPositional
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+BenchmarkIsFunction	 1000000	      2707 ns/op
+--- BENCH: BenchmarkIsFunction
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+BenchmarkIsSelection	   50000	     66976 ns/op
+--- BENCH: BenchmarkIsSelection
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+BenchmarkIsNodes	   50000	     66740 ns/op
+--- BENCH: BenchmarkIsNodes
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+BenchmarkHasClass	    5000	    701722 ns/op
+--- BENCH: BenchmarkHasClass
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+BenchmarkContains	100000000	        11.9 ns/op
+--- BENCH: BenchmarkContains
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+BenchmarkFind	   50000	     55444 ns/op
+--- BENCH: BenchmarkFind
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+BenchmarkFindWithinSelection	   10000	    127984 ns/op
+--- BENCH: BenchmarkFindWithinSelection
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+BenchmarkFindSelection	    5000	    355944 ns/op
+--- BENCH: BenchmarkFindSelection
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+BenchmarkFindNodes	    5000	    355596 ns/op
+--- BENCH: BenchmarkFindNodes
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+BenchmarkContents	  500000	      5656 ns/op
+--- BENCH: BenchmarkContents
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+BenchmarkContentsFiltered	  200000	      9007 ns/op
+--- BENCH: BenchmarkContentsFiltered
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+BenchmarkChildren	 1000000	      1237 ns/op
+--- BENCH: BenchmarkChildren
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+BenchmarkChildrenFiltered	  500000	      5613 ns/op
+--- BENCH: BenchmarkChildrenFiltered
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+BenchmarkParent	   50000	     47026 ns/op
+--- BENCH: BenchmarkParent
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+BenchmarkParentFiltered	   50000	     51438 ns/op
+--- BENCH: BenchmarkParentFiltered
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+BenchmarkParents	   20000	     91820 ns/op
+--- BENCH: BenchmarkParents
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+BenchmarkParentsFiltered	   20000	     95156 ns/op
+--- BENCH: BenchmarkParentsFiltered
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+BenchmarkParentsUntil	   10000	    134383 ns/op
+--- BENCH: BenchmarkParentsUntil
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+BenchmarkParentsUntilSelection	   10000	    235456 ns/op
+--- BENCH: BenchmarkParentsUntilSelection
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+BenchmarkParentsUntilNodes	   10000	    235936 ns/op
+--- BENCH: BenchmarkParentsUntilNodes
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+BenchmarkParentsFilteredUntil	   50000	     32451 ns/op
+--- BENCH: BenchmarkParentsFilteredUntil
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+BenchmarkParentsFilteredUntilSelection	   50000	     30570 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilSelection
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+BenchmarkParentsFilteredUntilNodes	   50000	     30729 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilNodes
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+BenchmarkSiblings	   10000	    106704 ns/op
+--- BENCH: BenchmarkSiblings
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+BenchmarkSiblingsFiltered	   10000	    115592 ns/op
+--- BENCH: BenchmarkSiblingsFiltered
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+BenchmarkNext	   50000	     54449 ns/op
+--- BENCH: BenchmarkNext
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+BenchmarkNextFiltered	   50000	     58503 ns/op
+--- BENCH: BenchmarkNextFiltered
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+BenchmarkNextAll	   20000	     77698 ns/op
+--- BENCH: BenchmarkNextAll
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+BenchmarkNextAllFiltered	   20000	     85034 ns/op
+--- BENCH: BenchmarkNextAllFiltered
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+BenchmarkPrev	   50000	     56458 ns/op
+--- BENCH: BenchmarkPrev
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+BenchmarkPrevFiltered	   50000	     60163 ns/op
+--- BENCH: BenchmarkPrevFiltered
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+BenchmarkPrevAll	   50000	     47679 ns/op
+--- BENCH: BenchmarkPrevAll
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+BenchmarkPrevAllFiltered	   50000	     51563 ns/op
+--- BENCH: BenchmarkPrevAllFiltered
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+BenchmarkNextUntil	   10000	    213998 ns/op
+--- BENCH: BenchmarkNextUntil
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+BenchmarkNextUntilSelection	   10000	    140720 ns/op
+--- BENCH: BenchmarkNextUntilSelection
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+BenchmarkNextUntilNodes	   20000	     90702 ns/op
+--- BENCH: BenchmarkNextUntilNodes
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+BenchmarkPrevUntil	    5000	    456039 ns/op
+--- BENCH: BenchmarkPrevUntil
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+BenchmarkPrevUntilSelection	   10000	    167944 ns/op
+--- BENCH: BenchmarkPrevUntilSelection
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+BenchmarkPrevUntilNodes	   20000	     82059 ns/op
+--- BENCH: BenchmarkPrevUntilNodes
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+BenchmarkNextFilteredUntil	   10000	    150883 ns/op
+--- BENCH: BenchmarkNextFilteredUntil
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+BenchmarkNextFilteredUntilSelection	   10000	    146578 ns/op
+--- BENCH: BenchmarkNextFilteredUntilSelection
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+BenchmarkNextFilteredUntilNodes	   10000	    148284 ns/op
+--- BENCH: BenchmarkNextFilteredUntilNodes
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+BenchmarkPrevFilteredUntil	   10000	    154303 ns/op
+--- BENCH: BenchmarkPrevFilteredUntil
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+BenchmarkPrevFilteredUntilSelection	   10000	    149062 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilSelection
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+BenchmarkPrevFilteredUntilNodes	   10000	    150584 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilNodes
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+ok  	github.com/PuerkitoBio/goquery	188.326s

+ 438 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.1.1

@@ -0,0 +1,438 @@
+PASS
+BenchmarkFirst	20000000	        96.2 ns/op
+BenchmarkLast	20000000	        95.8 ns/op
+BenchmarkEq	20000000	        94.4 ns/op
+BenchmarkSlice	20000000	        89.9 ns/op
+BenchmarkGet	1000000000	         2.31 ns/op
+BenchmarkIndex	 1000000	      1911 ns/op
+--- BENCH: BenchmarkIndex
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+BenchmarkIndexSelector	   50000	     56034 ns/op
+--- BENCH: BenchmarkIndexSelector
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+BenchmarkIndexOfNode	100000000	        11.8 ns/op
+--- BENCH: BenchmarkIndexOfNode
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+BenchmarkIndexOfSelection	100000000	        12.1 ns/op
+--- BENCH: BenchmarkIndexOfSelection
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+BenchmarkMetalReviewExample	    5000	    336823 ns/op
+--- BENCH: BenchmarkMetalReviewExample
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+		
+	bench_example_test.go:41: MetalReviewExample=10
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+	... [output truncated]
+BenchmarkAdd	   50000	     54709 ns/op
+--- BENCH: BenchmarkAdd
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+BenchmarkAddSelection	10000000	       209 ns/op
+--- BENCH: BenchmarkAddSelection
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+BenchmarkAddNodes	10000000	       202 ns/op
+--- BENCH: BenchmarkAddNodes
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+BenchmarkAndSelf	 1000000	      2634 ns/op
+--- BENCH: BenchmarkAndSelf
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+BenchmarkFilter	   50000	     31049 ns/op
+--- BENCH: BenchmarkFilter
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+BenchmarkNot	   50000	     35167 ns/op
+--- BENCH: BenchmarkNot
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+BenchmarkFilterFunction	   50000	     68974 ns/op
+--- BENCH: BenchmarkFilterFunction
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+BenchmarkNotFunction	   50000	     74760 ns/op
+--- BENCH: BenchmarkNotFunction
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+BenchmarkFilterNodes	   50000	     68670 ns/op
+--- BENCH: BenchmarkFilterNodes
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+BenchmarkNotNodes	   20000	     81357 ns/op
+--- BENCH: BenchmarkNotNodes
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+BenchmarkFilterSelection	   50000	     68388 ns/op
+--- BENCH: BenchmarkFilterSelection
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+BenchmarkNotSelection	   20000	     82108 ns/op
+--- BENCH: BenchmarkNotSelection
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+BenchmarkHas	    5000	    582934 ns/op
+--- BENCH: BenchmarkHas
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+BenchmarkHasNodes	   10000	    241602 ns/op
+--- BENCH: BenchmarkHasNodes
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+BenchmarkHasSelection	   10000	    243612 ns/op
+--- BENCH: BenchmarkHasSelection
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+BenchmarkEnd	500000000	         4.14 ns/op
+--- BENCH: BenchmarkEnd
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+BenchmarkEach	  200000	      9848 ns/op
+--- BENCH: BenchmarkEach
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+BenchmarkMap	  100000	     17569 ns/op
+--- BENCH: BenchmarkMap
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+BenchmarkAttr	50000000	        37.6 ns/op
+--- BENCH: BenchmarkAttr
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+BenchmarkText	  100000	     19345 ns/op
+BenchmarkLength	2000000000	         0.80 ns/op
+--- BENCH: BenchmarkLength
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+BenchmarkHtml	 5000000	       688 ns/op
+BenchmarkIs	   50000	     35061 ns/op
+--- BENCH: BenchmarkIs
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+BenchmarkIsPositional	   50000	     32789 ns/op
+--- BENCH: BenchmarkIsPositional
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+BenchmarkIsFunction	 1000000	      2816 ns/op
+--- BENCH: BenchmarkIsFunction
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+BenchmarkIsSelection	   50000	     68272 ns/op
+--- BENCH: BenchmarkIsSelection
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+BenchmarkIsNodes	   50000	     68107 ns/op
+--- BENCH: BenchmarkIsNodes
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+BenchmarkHasClass	    5000	    709386 ns/op
+--- BENCH: BenchmarkHasClass
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+BenchmarkContains	100000000	        12.4 ns/op
+--- BENCH: BenchmarkContains
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+BenchmarkFind	   50000	     56342 ns/op
+--- BENCH: BenchmarkFind
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+BenchmarkFindWithinSelection	   10000	    131878 ns/op
+--- BENCH: BenchmarkFindWithinSelection
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+BenchmarkFindSelection	    5000	    374240 ns/op
+--- BENCH: BenchmarkFindSelection
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+BenchmarkFindNodes	    5000	    374447 ns/op
+--- BENCH: BenchmarkFindNodes
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+BenchmarkContents	  200000	      9721 ns/op
+--- BENCH: BenchmarkContents
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+BenchmarkContentsFiltered	  200000	     12909 ns/op
+--- BENCH: BenchmarkContentsFiltered
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+BenchmarkChildren	 1000000	      1869 ns/op
+--- BENCH: BenchmarkChildren
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+BenchmarkChildrenFiltered	  500000	      5941 ns/op
+--- BENCH: BenchmarkChildrenFiltered
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+BenchmarkParent	   50000	     46223 ns/op
+--- BENCH: BenchmarkParent
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+BenchmarkParentFiltered	   50000	     51452 ns/op
+--- BENCH: BenchmarkParentFiltered
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+BenchmarkParents	   20000	     93967 ns/op
+--- BENCH: BenchmarkParents
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+BenchmarkParentsFiltered	   20000	     97617 ns/op
+--- BENCH: BenchmarkParentsFiltered
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+BenchmarkParentsUntil	   10000	    138898 ns/op
+--- BENCH: BenchmarkParentsUntil
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+BenchmarkParentsUntilSelection	   10000	    247817 ns/op
+--- BENCH: BenchmarkParentsUntilSelection
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+BenchmarkParentsUntilNodes	   10000	    246055 ns/op
+--- BENCH: BenchmarkParentsUntilNodes
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+BenchmarkParentsFilteredUntil	   50000	     33201 ns/op
+--- BENCH: BenchmarkParentsFilteredUntil
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+BenchmarkParentsFilteredUntilSelection	   50000	     31486 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilSelection
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+BenchmarkParentsFilteredUntilNodes	   50000	     31754 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilNodes
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+BenchmarkSiblings	   20000	     94749 ns/op
+--- BENCH: BenchmarkSiblings
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+BenchmarkSiblingsFiltered	   10000	    103926 ns/op
+--- BENCH: BenchmarkSiblingsFiltered
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+BenchmarkNext	   50000	     33782 ns/op
+--- BENCH: BenchmarkNext
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+BenchmarkNextFiltered	   50000	     37108 ns/op
+--- BENCH: BenchmarkNextFiltered
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+BenchmarkNextAll	   50000	     64769 ns/op
+--- BENCH: BenchmarkNextAll
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+BenchmarkNextAllFiltered	   50000	     71050 ns/op
+--- BENCH: BenchmarkNextAllFiltered
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+BenchmarkPrev	   50000	     33908 ns/op
+--- BENCH: BenchmarkPrev
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+BenchmarkPrevFiltered	   50000	     37353 ns/op
+--- BENCH: BenchmarkPrevFiltered
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+BenchmarkPrevAll	   50000	     31056 ns/op
+--- BENCH: BenchmarkPrevAll
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+BenchmarkPrevAllFiltered	   50000	     34286 ns/op
+--- BENCH: BenchmarkPrevAllFiltered
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+BenchmarkNextUntil	   10000	    202553 ns/op
+--- BENCH: BenchmarkNextUntil
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+BenchmarkNextUntilSelection	   20000	     98693 ns/op
+--- BENCH: BenchmarkNextUntilSelection
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+BenchmarkNextUntilNodes	   50000	     45532 ns/op
+--- BENCH: BenchmarkNextUntilNodes
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+BenchmarkPrevUntil	    5000	    454378 ns/op
+--- BENCH: BenchmarkPrevUntil
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+BenchmarkPrevUntilSelection	   10000	    123594 ns/op
+--- BENCH: BenchmarkPrevUntilSelection
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+BenchmarkPrevUntilNodes	   50000	     37509 ns/op
+--- BENCH: BenchmarkPrevUntilNodes
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+BenchmarkNextFilteredUntil	   10000	    109317 ns/op
+--- BENCH: BenchmarkNextFilteredUntil
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+BenchmarkNextFilteredUntilSelection	   10000	    105959 ns/op
+--- BENCH: BenchmarkNextFilteredUntilSelection
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+BenchmarkNextFilteredUntilNodes	   10000	    107132 ns/op
+--- BENCH: BenchmarkNextFilteredUntilNodes
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+BenchmarkPrevFilteredUntil	   10000	    114474 ns/op
+--- BENCH: BenchmarkPrevFilteredUntil
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+BenchmarkPrevFilteredUntilSelection	   10000	    107592 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilSelection
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+BenchmarkPrevFilteredUntilNodes	   10000	    107495 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilNodes
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+ok  	github.com/PuerkitoBio/goquery	187.652s

+ 405 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.1.1-v0.2.1-go1.1rc1.svg

@@ -0,0 +1,405 @@
+<?xml version="1.0"?>
+<!-- Generated by SVGo -->
+<svg width="1024" height="768"
+     xmlns="http://www.w3.org/2000/svg" 
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+<g style="font-size:20px;font-family:sans-serif">
+<rect x="0" y="0" width="1024" height="768" style="fill:white"/>
+<text x="100" y="50" style="font-size:150%"></text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="118" width="0" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="607" y="133" style="text-anchor:end">+0.10%</text>
+</g>
+<text x="100" y="138" style="text-anchor:start">BenchmarkFirst</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="144" width="0" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="617" y="159" style="text-anchor:start">-0.10%</text>
+</g>
+<text x="100" y="164" style="text-anchor:start">BenchmarkLast</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="604" y="170" width="8" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="599" y="185" style="text-anchor:end">+2.86%</text>
+</g>
+<text x="100" y="190" style="text-anchor:start">BenchmarkEq</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="604" y="196" width="8" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="599" y="211" style="text-anchor:end">+2.67%</text>
+</g>
+<text x="100" y="216" style="text-anchor:start">BenchmarkSlice</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="222" width="32" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="649" y="237" style="text-anchor:start">-10.82%</text>
+</g>
+<text x="100" y="242" style="text-anchor:start">BenchmarkGet</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="248" width="132" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="749" y="263" style="text-anchor:start">-44.06%</text>
+</g>
+<text x="100" y="268" style="text-anchor:start">BenchmarkIndex</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="274" width="156" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="773" y="289" style="text-anchor:start">-52.26%</text>
+</g>
+<text x="100" y="294" style="text-anchor:start">BenchmarkIndexSelector</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="300" width="27" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="644" y="315" style="text-anchor:start">-9.32%</text>
+</g>
+<text x="100" y="320" style="text-anchor:start">BenchmarkIndexOfNode</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="326" width="7" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="624" y="341" style="text-anchor:start">-2.48%</text>
+</g>
+<text x="100" y="346" style="text-anchor:start">BenchmarkIndexOfSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="352" width="104" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="721" y="367" style="text-anchor:start">-34.96%</text>
+</g>
+<text x="100" y="372" style="text-anchor:start">BenchmarkMetalReviewExample</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="378" width="177" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="794" y="393" style="text-anchor:start">-59.16%</text>
+</g>
+<text x="100" y="398" style="text-anchor:start">BenchmarkAdd</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="404" width="0" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="617" y="419" style="text-anchor:start">+0.00%</text>
+</g>
+<text x="100" y="424" style="text-anchor:start">BenchmarkAddSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="602" y="430" width="10" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="597" y="445" style="text-anchor:end">+3.47%</text>
+</g>
+<text x="100" y="450" style="text-anchor:start">BenchmarkAddNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="456" width="7" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="624" y="471" style="text-anchor:start">-2.51%</text>
+</g>
+<text x="100" y="476" style="text-anchor:start">BenchmarkAndSelf</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="482" width="41" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="658" y="497" style="text-anchor:start">-13.96%</text>
+</g>
+<text x="100" y="502" style="text-anchor:start">BenchmarkFilter</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="508" width="43" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="660" y="523" style="text-anchor:start">-14.53%</text>
+</g>
+<text x="100" y="528" style="text-anchor:start">BenchmarkNot</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="534" width="11" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="628" y="549" style="text-anchor:start">-3.81%</text>
+</g>
+<text x="100" y="554" style="text-anchor:start">BenchmarkFilterFunction</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="560" width="8" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="625" y="575" style="text-anchor:start">-2.83%</text>
+</g>
+<text x="100" y="580" style="text-anchor:start">BenchmarkNotFunction</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="586" width="13" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="630" y="601" style="text-anchor:start">-4.63%</text>
+</g>
+<text x="100" y="606" style="text-anchor:start">BenchmarkFilterNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="612" width="6" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="623" y="627" style="text-anchor:start">-2.32%</text>
+</g>
+<text x="100" y="632" style="text-anchor:start">BenchmarkNotNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="638" width="12" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="629" y="653" style="text-anchor:start">-4.23%</text>
+</g>
+<text x="100" y="658" style="text-anchor:start">BenchmarkFilterSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="664" width="9" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="626" y="679" style="text-anchor:start">-3.31%</text>
+</g>
+<text x="100" y="684" style="text-anchor:start">BenchmarkNotSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="690" width="101" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="718" y="705" style="text-anchor:start">-33.69%</text>
+</g>
+<text x="100" y="710" style="text-anchor:start">BenchmarkHas</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="716" width="13" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="630" y="731" style="text-anchor:start">-4.53%</text>
+</g>
+<text x="100" y="736" style="text-anchor:start">BenchmarkHasNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="742" width="15" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="632" y="757" style="text-anchor:start">-5.30%</text>
+</g>
+<text x="100" y="762" style="text-anchor:start">BenchmarkHasSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="578" y="768" width="34" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="573" y="783" style="text-anchor:end">+11.35%</text>
+</g>
+<text x="100" y="788" style="text-anchor:start">BenchmarkEnd</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="794" width="9" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="626" y="809" style="text-anchor:start">-3.33%</text>
+</g>
+<text x="100" y="814" style="text-anchor:start">BenchmarkEach</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="820" width="27" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="644" y="835" style="text-anchor:start">-9.16%</text>
+</g>
+<text x="100" y="840" style="text-anchor:start">BenchmarkMap</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="846" width="57" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="674" y="861" style="text-anchor:start">-19.15%</text>
+</g>
+<text x="100" y="866" style="text-anchor:start">BenchmarkAttr</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="872" width="9" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="626" y="887" style="text-anchor:start">-3.24%</text>
+</g>
+<text x="100" y="892" style="text-anchor:start">BenchmarkText</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="898" width="183" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="800" y="913" style="text-anchor:start">-61.25%</text>
+</g>
+<text x="100" y="918" style="text-anchor:start">BenchmarkLength</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="924" width="35" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="652" y="939" style="text-anchor:start">-11.92%</text>
+</g>
+<text x="100" y="944" style="text-anchor:start">BenchmarkHtml</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="950" width="49" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="666" y="965" style="text-anchor:start">-16.46%</text>
+</g>
+<text x="100" y="970" style="text-anchor:start">BenchmarkIs</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="976" width="81" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="698" y="991" style="text-anchor:start">-27.31%</text>
+</g>
+<text x="100" y="996" style="text-anchor:start">BenchmarkIsPositional</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1002" width="40" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="657" y="1017" style="text-anchor:start">-13.49%</text>
+</g>
+<text x="100" y="1022" style="text-anchor:start">BenchmarkIsFunction</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1028" width="5" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="622" y="1043" style="text-anchor:start">-1.71%</text>
+</g>
+<text x="100" y="1048" style="text-anchor:start">BenchmarkIsSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1054" width="9" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="626" y="1069" style="text-anchor:start">-3.03%</text>
+</g>
+<text x="100" y="1074" style="text-anchor:start">BenchmarkIsNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1080" width="60" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="677" y="1095" style="text-anchor:start">-20.30%</text>
+</g>
+<text x="100" y="1100" style="text-anchor:start">BenchmarkHasClass</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1106" width="33" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="650" y="1121" style="text-anchor:start">-11.29%</text>
+</g>
+<text x="100" y="1126" style="text-anchor:start">BenchmarkContains</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1132" width="152" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="769" y="1147" style="text-anchor:start">-50.88%</text>
+</g>
+<text x="100" y="1152" style="text-anchor:start">BenchmarkFind</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1158" width="135" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="752" y="1173" style="text-anchor:start">-45.28%</text>
+</g>
+<text x="100" y="1178" style="text-anchor:start">BenchmarkFindWithinSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="544" y="1184" width="68" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="539" y="1199" style="text-anchor:end">+22.95%</text>
+</g>
+<text x="100" y="1204" style="text-anchor:start">BenchmarkFindSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="544" y="1210" width="68" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="539" y="1225" style="text-anchor:end">+22.68%</text>
+</g>
+<text x="100" y="1230" style="text-anchor:start">BenchmarkFindNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1236" width="195" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="812" y="1251" style="text-anchor:start">-65.08%</text>
+</g>
+<text x="100" y="1256" style="text-anchor:start">BenchmarkContents</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1262" width="179" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="796" y="1277" style="text-anchor:start">-59.77%</text>
+</g>
+<text x="100" y="1282" style="text-anchor:start">BenchmarkContentsFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1288" width="192" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="809" y="1303" style="text-anchor:start">-64.31%</text>
+</g>
+<text x="100" y="1308" style="text-anchor:start">BenchmarkChildren</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1314" width="118" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="735" y="1329" style="text-anchor:start">-39.56%</text>
+</g>
+<text x="100" y="1334" style="text-anchor:start">BenchmarkChildrenFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1340" width="19" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="636" y="1355" style="text-anchor:start">-6.61%</text>
+</g>
+<text x="100" y="1360" style="text-anchor:start">BenchmarkParent</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1366" width="32" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="649" y="1381" style="text-anchor:start">-10.92%</text>
+</g>
+<text x="100" y="1386" style="text-anchor:start">BenchmarkParentFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1392" width="5" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="622" y="1407" style="text-anchor:start">-1.72%</text>
+</g>
+<text x="100" y="1412" style="text-anchor:start">BenchmarkParents</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1418" width="7" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="624" y="1433" style="text-anchor:start">-2.37%</text>
+</g>
+<text x="100" y="1438" style="text-anchor:start">BenchmarkParentsFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1444" width="142" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="759" y="1459" style="text-anchor:start">-47.60%</text>
+</g>
+<text x="100" y="1464" style="text-anchor:start">BenchmarkParentsUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1470" width="109" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="726" y="1485" style="text-anchor:start">-36.39%</text>
+</g>
+<text x="100" y="1490" style="text-anchor:start">BenchmarkParentsUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1496" width="107" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="724" y="1511" style="text-anchor:start">-35.99%</text>
+</g>
+<text x="100" y="1516" style="text-anchor:start">BenchmarkParentsUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1522" width="146" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="763" y="1537" style="text-anchor:start">-48.84%</text>
+</g>
+<text x="100" y="1542" style="text-anchor:start">BenchmarkParentsFilteredUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1548" width="98" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="715" y="1563" style="text-anchor:start">-32.75%</text>
+</g>
+<text x="100" y="1568" style="text-anchor:start">BenchmarkParentsFilteredUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1574" width="99" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="716" y="1589" style="text-anchor:start">-33.12%</text>
+</g>
+<text x="100" y="1594" style="text-anchor:start">BenchmarkParentsFilteredUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1600" width="89" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="706" y="1615" style="text-anchor:start">-29.78%</text>
+</g>
+<text x="100" y="1620" style="text-anchor:start">BenchmarkSiblings</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1626" width="92" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="709" y="1641" style="text-anchor:start">-30.89%</text>
+</g>
+<text x="100" y="1646" style="text-anchor:start">BenchmarkSiblingsFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1652" width="204" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="821" y="1667" style="text-anchor:start">-68.19%</text>
+</g>
+<text x="100" y="1672" style="text-anchor:start">BenchmarkNext</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1678" width="199" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="816" y="1693" style="text-anchor:start">-66.59%</text>
+</g>
+<text x="100" y="1698" style="text-anchor:start">BenchmarkNextFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1704" width="93" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="710" y="1719" style="text-anchor:start">-31.08%</text>
+</g>
+<text x="100" y="1724" style="text-anchor:start">BenchmarkNextAll</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1730" width="90" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="707" y="1745" style="text-anchor:start">-30.03%</text>
+</g>
+<text x="100" y="1750" style="text-anchor:start">BenchmarkNextAllFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1756" width="204" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="821" y="1771" style="text-anchor:start">-68.32%</text>
+</g>
+<text x="100" y="1776" style="text-anchor:start">BenchmarkPrev</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1782" width="199" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="816" y="1797" style="text-anchor:start">-66.65%</text>
+</g>
+<text x="100" y="1802" style="text-anchor:start">BenchmarkPrevFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1808" width="129" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="746" y="1823" style="text-anchor:start">-43.33%</text>
+</g>
+<text x="100" y="1828" style="text-anchor:start">BenchmarkPrevAll</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1834" width="129" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="746" y="1849" style="text-anchor:start">-43.24%</text>
+</g>
+<text x="100" y="1854" style="text-anchor:start">BenchmarkPrevAllFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1860" width="162" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="779" y="1875" style="text-anchor:start">-54.27%</text>
+</g>
+<text x="100" y="1880" style="text-anchor:start">BenchmarkNextUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1886" width="113" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="730" y="1901" style="text-anchor:start">-37.90%</text>
+</g>
+<text x="100" y="1906" style="text-anchor:start">BenchmarkNextUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1912" width="129" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="746" y="1927" style="text-anchor:start">-43.21%</text>
+</g>
+<text x="100" y="1932" style="text-anchor:start">BenchmarkNextUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1938" width="147" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="764" y="1953" style="text-anchor:start">-49.33%</text>
+</g>
+<text x="100" y="1958" style="text-anchor:start">BenchmarkPrevUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1964" width="111" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="728" y="1979" style="text-anchor:start">-37.02%</text>
+</g>
+<text x="100" y="1984" style="text-anchor:start">BenchmarkPrevUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1990" width="133" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="750" y="2005" style="text-anchor:start">-44.59%</text>
+</g>
+<text x="100" y="2010" style="text-anchor:start">BenchmarkPrevUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2016" width="173" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="790" y="2031" style="text-anchor:start">-57.79%</text>
+</g>
+<text x="100" y="2036" style="text-anchor:start">BenchmarkNextFilteredUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2042" width="109" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="726" y="2057" style="text-anchor:start">-36.61%</text>
+</g>
+<text x="100" y="2062" style="text-anchor:start">BenchmarkNextFilteredUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2068" width="113" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="730" y="2083" style="text-anchor:start">-37.81%</text>
+</g>
+<text x="100" y="2088" style="text-anchor:start">BenchmarkNextFilteredUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2094" width="177" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="794" y="2109" style="text-anchor:start">-59.21%</text>
+</g>
+<text x="100" y="2114" style="text-anchor:start">BenchmarkPrevFilteredUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2120" width="108" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="725" y="2135" style="text-anchor:start">-36.20%</text>
+</g>
+<text x="100" y="2140" style="text-anchor:start">BenchmarkPrevFilteredUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2146" width="108" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="725" y="2161" style="text-anchor:start">-36.05%</text>
+</g>
+<text x="100" y="2166" style="text-anchor:start">BenchmarkPrevFilteredUntilNodes</text>
+</g>
+</svg>

+ 459 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.2.0

@@ -0,0 +1,459 @@
+PASS
+BenchmarkFirst	20000000	        94.3 ns/op
+BenchmarkLast	20000000	        94.7 ns/op
+BenchmarkEq	20000000	        93.7 ns/op
+BenchmarkSlice	20000000	        89.9 ns/op
+BenchmarkGet	1000000000	         2.72 ns/op
+BenchmarkIndex	 1000000	      1834 ns/op
+--- BENCH: BenchmarkIndex
+bench_array_test.go:73: 	Index=3
+bench_array_test.go:73: 	Index=3
+bench_array_test.go:73: 	Index=3
+bench_array_test.go:73: 	Index=3
+BenchmarkIndexSelector	   50000	     53958 ns/op
+--- BENCH: BenchmarkIndexSelector
+bench_array_test.go:85: 	IndexSelector=4
+bench_array_test.go:85: 	IndexSelector=4
+bench_array_test.go:85: 	IndexSelector=4
+bench_array_test.go:85: 	IndexSelector=4
+BenchmarkIndexOfNode	100000000	        10.1 ns/op
+--- BENCH: BenchmarkIndexOfNode
+bench_array_test.go:99: 	IndexOfNode=2
+bench_array_test.go:99: 	IndexOfNode=2
+bench_array_test.go:99: 	IndexOfNode=2
+bench_array_test.go:99: 	IndexOfNode=2
+bench_array_test.go:99: 	IndexOfNode=2
+BenchmarkIndexOfSelection	100000000	        10.9 ns/op
+--- BENCH: BenchmarkIndexOfSelection
+bench_array_test.go:111: 	IndexOfSelection=2
+bench_array_test.go:111: 	IndexOfSelection=2
+bench_array_test.go:111: 	IndexOfSelection=2
+bench_array_test.go:111: 	IndexOfSelection=2
+bench_array_test.go:111: 	IndexOfSelection=2
+BenchmarkMetalReviewExample	    5000	    326712 ns/op
+--- BENCH: BenchmarkMetalReviewExample
+bench_example_test.go:40: 	Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+		
+		bench_example_test.go:41: 	MetalReviewExample=10
+bench_example_test.go:40: 	Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+	... [output truncated]
+BenchmarkAdd	   50000	     51776 ns/op
+--- BENCH: BenchmarkAdd
+bench_expand_test.go:20: 	Add=43
+bench_expand_test.go:20: 	Add=43
+bench_expand_test.go:20: 	Add=43
+bench_expand_test.go:20: 	Add=43
+BenchmarkAddSelection	10000000	       196 ns/op
+--- BENCH: BenchmarkAddSelection
+bench_expand_test.go:37: 	AddSelection=43
+bench_expand_test.go:37: 	AddSelection=43
+bench_expand_test.go:37: 	AddSelection=43
+bench_expand_test.go:37: 	AddSelection=43
+bench_expand_test.go:37: 	AddSelection=43
+BenchmarkAddNodes	10000000	       191 ns/op
+--- BENCH: BenchmarkAddNodes
+bench_expand_test.go:55: 	AddNodes=43
+bench_expand_test.go:55: 	AddNodes=43
+bench_expand_test.go:55: 	AddNodes=43
+bench_expand_test.go:55: 	AddNodes=43
+bench_expand_test.go:55: 	AddNodes=43
+BenchmarkAndSelf	 1000000	      2495 ns/op
+--- BENCH: BenchmarkAndSelf
+bench_expand_test.go:71: 	AndSelf=44
+bench_expand_test.go:71: 	AndSelf=44
+bench_expand_test.go:71: 	AndSelf=44
+bench_expand_test.go:71: 	AndSelf=44
+BenchmarkFilter	   50000	     30974 ns/op
+--- BENCH: BenchmarkFilter
+bench_filter_test.go:20: 	Filter=13
+bench_filter_test.go:20: 	Filter=13
+bench_filter_test.go:20: 	Filter=13
+bench_filter_test.go:20: 	Filter=13
+BenchmarkNot	   50000	     35322 ns/op
+--- BENCH: BenchmarkNot
+bench_filter_test.go:36: 	Not=371
+bench_filter_test.go:36: 	Not=371
+bench_filter_test.go:36: 	Not=371
+bench_filter_test.go:36: 	Not=371
+BenchmarkFilterFunction	   50000	     65644 ns/op
+--- BENCH: BenchmarkFilterFunction
+bench_filter_test.go:55: 	FilterFunction=112
+bench_filter_test.go:55: 	FilterFunction=112
+bench_filter_test.go:55: 	FilterFunction=112
+bench_filter_test.go:55: 	FilterFunction=112
+BenchmarkNotFunction	   50000	     69245 ns/op
+--- BENCH: BenchmarkNotFunction
+bench_filter_test.go:74: 	NotFunction=261
+bench_filter_test.go:74: 	NotFunction=261
+bench_filter_test.go:74: 	NotFunction=261
+bench_filter_test.go:74: 	NotFunction=261
+BenchmarkFilterNodes	   50000	     64824 ns/op
+--- BENCH: BenchmarkFilterNodes
+bench_filter_test.go:92: 	FilterNodes=2
+bench_filter_test.go:92: 	FilterNodes=2
+bench_filter_test.go:92: 	FilterNodes=2
+bench_filter_test.go:92: 	FilterNodes=2
+BenchmarkNotNodes	   20000	     76247 ns/op
+--- BENCH: BenchmarkNotNodes
+bench_filter_test.go:110: 	NotNodes=360
+bench_filter_test.go:110: 	NotNodes=360
+bench_filter_test.go:110: 	NotNodes=360
+bench_filter_test.go:110: 	NotNodes=360
+BenchmarkFilterSelection	   50000	     66154 ns/op
+--- BENCH: BenchmarkFilterSelection
+bench_filter_test.go:127: 	FilterSelection=2
+bench_filter_test.go:127: 	FilterSelection=2
+bench_filter_test.go:127: 	FilterSelection=2
+bench_filter_test.go:127: 	FilterSelection=2
+BenchmarkNotSelection	   20000	     76336 ns/op
+--- BENCH: BenchmarkNotSelection
+bench_filter_test.go:144: 	NotSelection=360
+bench_filter_test.go:144: 	NotSelection=360
+bench_filter_test.go:144: 	NotSelection=360
+bench_filter_test.go:144: 	NotSelection=360
+BenchmarkHas	    5000	    569495 ns/op
+--- BENCH: BenchmarkHas
+bench_filter_test.go:160: 	Has=13
+bench_filter_test.go:160: 	Has=13
+bench_filter_test.go:160: 	Has=13
+BenchmarkHasNodes	   10000	    227059 ns/op
+--- BENCH: BenchmarkHasNodes
+bench_filter_test.go:178: 	HasNodes=15
+bench_filter_test.go:178: 	HasNodes=15
+bench_filter_test.go:178: 	HasNodes=15
+BenchmarkHasSelection	   10000	    227167 ns/op
+--- BENCH: BenchmarkHasSelection
+bench_filter_test.go:195: 	HasSelection=15
+bench_filter_test.go:195: 	HasSelection=15
+bench_filter_test.go:195: 	HasSelection=15
+BenchmarkEnd	500000000	         3.99 ns/op
+--- BENCH: BenchmarkEnd
+bench_filter_test.go:211: 	End=373
+bench_filter_test.go:211: 	End=373
+bench_filter_test.go:211: 	End=373
+bench_filter_test.go:211: 	End=373
+bench_filter_test.go:211: 	End=373
+bench_filter_test.go:211: 	End=373
+BenchmarkEach	  200000	      9354 ns/op
+--- BENCH: BenchmarkEach
+bench_iteration_test.go:22: 	Each=59
+bench_iteration_test.go:22: 	Each=59
+bench_iteration_test.go:22: 	Each=59
+bench_iteration_test.go:22: 	Each=59
+BenchmarkMap	  100000	     16557 ns/op
+--- BENCH: BenchmarkMap
+bench_iteration_test.go:41: 	Map=59
+bench_iteration_test.go:41: 	Map=59
+bench_iteration_test.go:41: 	Map=59
+bench_iteration_test.go:41: 	Map=59
+BenchmarkAttr	50000000	        36.4 ns/op
+--- BENCH: BenchmarkAttr
+bench_property_test.go:16: 	Attr=firstHeading
+bench_property_test.go:16: 	Attr=firstHeading
+bench_property_test.go:16: 	Attr=firstHeading
+bench_property_test.go:16: 	Attr=firstHeading
+bench_property_test.go:16: 	Attr=firstHeading
+BenchmarkText	  100000	     18473 ns/op
+BenchmarkLength	2000000000	         0.76 ns/op
+--- BENCH: BenchmarkLength
+bench_property_test.go:37: 	Length=14
+bench_property_test.go:37: 	Length=14
+bench_property_test.go:37: 	Length=14
+bench_property_test.go:37: 	Length=14
+bench_property_test.go:37: 	Length=14
+bench_property_test.go:37: 	Length=14
+BenchmarkHtml	 5000000	       666 ns/op
+BenchmarkIs	   50000	     35174 ns/op
+--- BENCH: BenchmarkIs
+bench_query_test.go:16: 	Is=true
+bench_query_test.go:16: 	Is=true
+bench_query_test.go:16: 	Is=true
+bench_query_test.go:16: 	Is=true
+BenchmarkIsPositional	   50000	     31814 ns/op
+--- BENCH: BenchmarkIsPositional
+bench_query_test.go:28: 	IsPositional=true
+bench_query_test.go:28: 	IsPositional=true
+bench_query_test.go:28: 	IsPositional=true
+bench_query_test.go:28: 	IsPositional=true
+BenchmarkIsFunction	 1000000	      2754 ns/op
+--- BENCH: BenchmarkIsFunction
+bench_query_test.go:43: 	IsFunction=true
+bench_query_test.go:43: 	IsFunction=true
+bench_query_test.go:43: 	IsFunction=true
+bench_query_test.go:43: 	IsFunction=true
+BenchmarkIsSelection	   50000	     66260 ns/op
+--- BENCH: BenchmarkIsSelection
+bench_query_test.go:56: 	IsSelection=true
+bench_query_test.go:56: 	IsSelection=true
+bench_query_test.go:56: 	IsSelection=true
+bench_query_test.go:56: 	IsSelection=true
+BenchmarkIsNodes	   50000	     64682 ns/op
+--- BENCH: BenchmarkIsNodes
+bench_query_test.go:70: 	IsNodes=true
+bench_query_test.go:70: 	IsNodes=true
+bench_query_test.go:70: 	IsNodes=true
+bench_query_test.go:70: 	IsNodes=true
+BenchmarkHasClass	    5000	    672953 ns/op
+--- BENCH: BenchmarkHasClass
+bench_query_test.go:82: 	HasClass=true
+bench_query_test.go:82: 	HasClass=true
+bench_query_test.go:82: 	HasClass=true
+BenchmarkContains	100000000	        11.3 ns/op
+--- BENCH: BenchmarkContains
+bench_query_test.go:96: 	Contains=true
+bench_query_test.go:96: 	Contains=true
+bench_query_test.go:96: 	Contains=true
+bench_query_test.go:96: 	Contains=true
+bench_query_test.go:96: 	Contains=true
+BenchmarkFind	   50000	     53780 ns/op
+--- BENCH: BenchmarkFind
+bench_traversal_test.go:18: 	Find=41
+bench_traversal_test.go:18: 	Find=41
+bench_traversal_test.go:18: 	Find=41
+bench_traversal_test.go:18: 	Find=41
+BenchmarkFindWithinSelection	   10000	    125963 ns/op
+--- BENCH: BenchmarkFindWithinSelection
+bench_traversal_test.go:34: 	FindWithinSelection=39
+bench_traversal_test.go:34: 	FindWithinSelection=39
+bench_traversal_test.go:34: 	FindWithinSelection=39
+BenchmarkFindSelection	    5000	    357318 ns/op
+--- BENCH: BenchmarkFindSelection
+bench_traversal_test.go:51: 	FindSelection=73
+bench_traversal_test.go:51: 	FindSelection=73
+bench_traversal_test.go:51: 	FindSelection=73
+BenchmarkFindNodes	    5000	    357587 ns/op
+--- BENCH: BenchmarkFindNodes
+bench_traversal_test.go:69: 	FindNodes=73
+bench_traversal_test.go:69: 	FindNodes=73
+bench_traversal_test.go:69: 	FindNodes=73
+BenchmarkContents	  200000	      9135 ns/op
+--- BENCH: BenchmarkContents
+bench_traversal_test.go:85: 	Contents=16
+bench_traversal_test.go:85: 	Contents=16
+bench_traversal_test.go:85: 	Contents=16
+bench_traversal_test.go:85: 	Contents=16
+BenchmarkContentsFiltered	  200000	     12383 ns/op
+--- BENCH: BenchmarkContentsFiltered
+bench_traversal_test.go:101: 	ContentsFiltered=1
+bench_traversal_test.go:101: 	ContentsFiltered=1
+bench_traversal_test.go:101: 	ContentsFiltered=1
+bench_traversal_test.go:101: 	ContentsFiltered=1
+BenchmarkChildren	 1000000	      1809 ns/op
+--- BENCH: BenchmarkChildren
+bench_traversal_test.go:117: 	Children=2
+bench_traversal_test.go:117: 	Children=2
+bench_traversal_test.go:117: 	Children=2
+bench_traversal_test.go:117: 	Children=2
+BenchmarkChildrenFiltered	  500000	      5814 ns/op
+--- BENCH: BenchmarkChildrenFiltered
+bench_traversal_test.go:133: 	ChildrenFiltered=2
+bench_traversal_test.go:133: 	ChildrenFiltered=2
+bench_traversal_test.go:133: 	ChildrenFiltered=2
+bench_traversal_test.go:133: 	ChildrenFiltered=2
+BenchmarkParent	   50000	     44810 ns/op
+--- BENCH: BenchmarkParent
+bench_traversal_test.go:149: 	Parent=55
+bench_traversal_test.go:149: 	Parent=55
+bench_traversal_test.go:149: 	Parent=55
+bench_traversal_test.go:149: 	Parent=55
+BenchmarkParentFiltered	   50000	     48795 ns/op
+--- BENCH: BenchmarkParentFiltered
+bench_traversal_test.go:165: 	ParentFiltered=4
+bench_traversal_test.go:165: 	ParentFiltered=4
+bench_traversal_test.go:165: 	ParentFiltered=4
+bench_traversal_test.go:165: 	ParentFiltered=4
+BenchmarkParents	   20000	     89102 ns/op
+--- BENCH: BenchmarkParents
+bench_traversal_test.go:181: 	Parents=73
+bench_traversal_test.go:181: 	Parents=73
+bench_traversal_test.go:181: 	Parents=73
+bench_traversal_test.go:181: 	Parents=73
+BenchmarkParentsFiltered	   20000	     93953 ns/op
+--- BENCH: BenchmarkParentsFiltered
+bench_traversal_test.go:197: 	ParentsFiltered=18
+bench_traversal_test.go:197: 	ParentsFiltered=18
+bench_traversal_test.go:197: 	ParentsFiltered=18
+bench_traversal_test.go:197: 	ParentsFiltered=18
+BenchmarkParentsUntil	   10000	    130783 ns/op
+--- BENCH: BenchmarkParentsUntil
+bench_traversal_test.go:213: 	ParentsUntil=52
+bench_traversal_test.go:213: 	ParentsUntil=52
+bench_traversal_test.go:213: 	ParentsUntil=52
+BenchmarkParentsUntilSelection	   10000	    231797 ns/op
+--- BENCH: BenchmarkParentsUntilSelection
+bench_traversal_test.go:230: 	ParentsUntilSelection=70
+bench_traversal_test.go:230: 	ParentsUntilSelection=70
+bench_traversal_test.go:230: 	ParentsUntilSelection=70
+BenchmarkParentsUntilNodes	   10000	    233761 ns/op
+--- BENCH: BenchmarkParentsUntilNodes
+bench_traversal_test.go:248: 	ParentsUntilNodes=70
+bench_traversal_test.go:248: 	ParentsUntilNodes=70
+bench_traversal_test.go:248: 	ParentsUntilNodes=70
+BenchmarkParentsFilteredUntil	   50000	     31360 ns/op
+--- BENCH: BenchmarkParentsFilteredUntil
+bench_traversal_test.go:264: 	ParentsFilteredUntil=2
+bench_traversal_test.go:264: 	ParentsFilteredUntil=2
+bench_traversal_test.go:264: 	ParentsFilteredUntil=2
+bench_traversal_test.go:264: 	ParentsFilteredUntil=2
+BenchmarkParentsFilteredUntilSelection	   50000	     30272 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilSelection
+bench_traversal_test.go:281: 	ParentsFilteredUntilSelection=2
+bench_traversal_test.go:281: 	ParentsFilteredUntilSelection=2
+bench_traversal_test.go:281: 	ParentsFilteredUntilSelection=2
+bench_traversal_test.go:281: 	ParentsFilteredUntilSelection=2
+BenchmarkParentsFilteredUntilNodes	   50000	     30327 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilNodes
+bench_traversal_test.go:299: 	ParentsFilteredUntilNodes=2
+bench_traversal_test.go:299: 	ParentsFilteredUntilNodes=2
+bench_traversal_test.go:299: 	ParentsFilteredUntilNodes=2
+bench_traversal_test.go:299: 	ParentsFilteredUntilNodes=2
+BenchmarkSiblings	   20000	     89862 ns/op
+--- BENCH: BenchmarkSiblings
+bench_traversal_test.go:315: 	Siblings=293
+bench_traversal_test.go:315: 	Siblings=293
+bench_traversal_test.go:315: 	Siblings=293
+bench_traversal_test.go:315: 	Siblings=293
+BenchmarkSiblingsFiltered	   20000	     97948 ns/op
+--- BENCH: BenchmarkSiblingsFiltered
+bench_traversal_test.go:331: 	SiblingsFiltered=46
+bench_traversal_test.go:331: 	SiblingsFiltered=46
+bench_traversal_test.go:331: 	SiblingsFiltered=46
+bench_traversal_test.go:331: 	SiblingsFiltered=46
+BenchmarkNext	   50000	     31975 ns/op
+--- BENCH: BenchmarkNext
+bench_traversal_test.go:347: 	Next=49
+bench_traversal_test.go:347: 	Next=49
+bench_traversal_test.go:347: 	Next=49
+bench_traversal_test.go:347: 	Next=49
+BenchmarkNextFiltered	   50000	     34887 ns/op
+--- BENCH: BenchmarkNextFiltered
+bench_traversal_test.go:363: 	NextFiltered=6
+bench_traversal_test.go:363: 	NextFiltered=6
+bench_traversal_test.go:363: 	NextFiltered=6
+bench_traversal_test.go:363: 	NextFiltered=6
+BenchmarkNextAll	   50000	     60734 ns/op
+--- BENCH: BenchmarkNextAll
+bench_traversal_test.go:379: 	NextAll=234
+bench_traversal_test.go:379: 	NextAll=234
+bench_traversal_test.go:379: 	NextAll=234
+bench_traversal_test.go:379: 	NextAll=234
+BenchmarkNextAllFiltered	   50000	     67428 ns/op
+--- BENCH: BenchmarkNextAllFiltered
+bench_traversal_test.go:395: 	NextAllFiltered=33
+bench_traversal_test.go:395: 	NextAllFiltered=33
+bench_traversal_test.go:395: 	NextAllFiltered=33
+bench_traversal_test.go:395: 	NextAllFiltered=33
+BenchmarkPrev	   50000	     32399 ns/op
+--- BENCH: BenchmarkPrev
+bench_traversal_test.go:411: 	Prev=49
+bench_traversal_test.go:411: 	Prev=49
+bench_traversal_test.go:411: 	Prev=49
+bench_traversal_test.go:411: 	Prev=49
+BenchmarkPrevFiltered	   50000	     34944 ns/op
+--- BENCH: BenchmarkPrevFiltered
+bench_traversal_test.go:429: 	PrevFiltered=7
+bench_traversal_test.go:429: 	PrevFiltered=7
+bench_traversal_test.go:429: 	PrevFiltered=7
+bench_traversal_test.go:429: 	PrevFiltered=7
+BenchmarkPrevAll	  100000	     29360 ns/op
+--- BENCH: BenchmarkPrevAll
+bench_traversal_test.go:445: 	PrevAll=78
+bench_traversal_test.go:445: 	PrevAll=78
+bench_traversal_test.go:445: 	PrevAll=78
+bench_traversal_test.go:445: 	PrevAll=78
+BenchmarkPrevAllFiltered	   50000	     32291 ns/op
+--- BENCH: BenchmarkPrevAllFiltered
+bench_traversal_test.go:461: 	PrevAllFiltered=6
+bench_traversal_test.go:461: 	PrevAllFiltered=6
+bench_traversal_test.go:461: 	PrevAllFiltered=6
+bench_traversal_test.go:461: 	PrevAllFiltered=6
+BenchmarkNextUntil	   10000	    191890 ns/op
+--- BENCH: BenchmarkNextUntil
+bench_traversal_test.go:477: 	NextUntil=84
+bench_traversal_test.go:477: 	NextUntil=84
+bench_traversal_test.go:477: 	NextUntil=84
+BenchmarkNextUntilSelection	   20000	     92054 ns/op
+--- BENCH: BenchmarkNextUntilSelection
+bench_traversal_test.go:494: 	NextUntilSelection=42
+bench_traversal_test.go:494: 	NextUntilSelection=42
+bench_traversal_test.go:494: 	NextUntilSelection=42
+bench_traversal_test.go:494: 	NextUntilSelection=42
+BenchmarkNextUntilNodes	   50000	     43401 ns/op
+--- BENCH: BenchmarkNextUntilNodes
+bench_traversal_test.go:512: 	NextUntilNodes=12
+bench_traversal_test.go:512: 	NextUntilNodes=12
+bench_traversal_test.go:512: 	NextUntilNodes=12
+bench_traversal_test.go:512: 	NextUntilNodes=12
+BenchmarkPrevUntil	    5000	    433383 ns/op
+--- BENCH: BenchmarkPrevUntil
+bench_traversal_test.go:528: 	PrevUntil=238
+bench_traversal_test.go:528: 	PrevUntil=238
+bench_traversal_test.go:528: 	PrevUntil=238
+BenchmarkPrevUntilSelection	   10000	    116423 ns/op
+--- BENCH: BenchmarkPrevUntilSelection
+bench_traversal_test.go:545: 	PrevUntilSelection=49
+bench_traversal_test.go:545: 	PrevUntilSelection=49
+bench_traversal_test.go:545: 	PrevUntilSelection=49
+BenchmarkPrevUntilNodes	   50000	     35338 ns/op
+--- BENCH: BenchmarkPrevUntilNodes
+bench_traversal_test.go:563: 	PrevUntilNodes=11
+bench_traversal_test.go:563: 	PrevUntilNodes=11
+bench_traversal_test.go:563: 	PrevUntilNodes=11
+bench_traversal_test.go:563: 	PrevUntilNodes=11
+BenchmarkNextFilteredUntil	   10000	    104686 ns/op
+--- BENCH: BenchmarkNextFilteredUntil
+bench_traversal_test.go:579: 	NextFilteredUntil=22
+bench_traversal_test.go:579: 	NextFilteredUntil=22
+bench_traversal_test.go:579: 	NextFilteredUntil=22
+BenchmarkNextFilteredUntilSelection	   20000	     99485 ns/op
+--- BENCH: BenchmarkNextFilteredUntilSelection
+bench_traversal_test.go:596: 	NextFilteredUntilSelection=22
+bench_traversal_test.go:596: 	NextFilteredUntilSelection=22
+bench_traversal_test.go:596: 	NextFilteredUntilSelection=22
+bench_traversal_test.go:596: 	NextFilteredUntilSelection=22
+BenchmarkNextFilteredUntilNodes	   20000	     99452 ns/op
+--- BENCH: BenchmarkNextFilteredUntilNodes
+bench_traversal_test.go:614: 	NextFilteredUntilNodes=22
+bench_traversal_test.go:614: 	NextFilteredUntilNodes=22
+bench_traversal_test.go:614: 	NextFilteredUntilNodes=22
+bench_traversal_test.go:614: 	NextFilteredUntilNodes=22
+BenchmarkPrevFilteredUntil	   10000	    112640 ns/op
+--- BENCH: BenchmarkPrevFilteredUntil
+bench_traversal_test.go:630: 	PrevFilteredUntil=20
+bench_traversal_test.go:630: 	PrevFilteredUntil=20
+bench_traversal_test.go:630: 	PrevFilteredUntil=20
+BenchmarkPrevFilteredUntilSelection	   10000	    103702 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilSelection
+bench_traversal_test.go:647: 	PrevFilteredUntilSelection=20
+bench_traversal_test.go:647: 	PrevFilteredUntilSelection=20
+bench_traversal_test.go:647: 	PrevFilteredUntilSelection=20
+BenchmarkPrevFilteredUntilNodes	   10000	    103277 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilNodes
+bench_traversal_test.go:665: 	PrevFilteredUntilNodes=20
+bench_traversal_test.go:665: 	PrevFilteredUntilNodes=20
+bench_traversal_test.go:665: 	PrevFilteredUntilNodes=20
+BenchmarkClosest	  500000	      6530 ns/op
+--- BENCH: BenchmarkClosest
+bench_traversal_test.go:681: 	Closest=2
+bench_traversal_test.go:681: 	Closest=2
+bench_traversal_test.go:681: 	Closest=2
+bench_traversal_test.go:681: 	Closest=2
+BenchmarkClosestSelection	 1000000	      1135 ns/op
+--- BENCH: BenchmarkClosestSelection
+bench_traversal_test.go:698: 	ClosestSelection=2
+bench_traversal_test.go:698: 	ClosestSelection=2
+bench_traversal_test.go:698: 	ClosestSelection=2
+bench_traversal_test.go:698: 	ClosestSelection=2
+BenchmarkClosestNodes	 1000000	      1133 ns/op
+--- BENCH: BenchmarkClosestNodes
+bench_traversal_test.go:715: 	ClosestNodes=2
+bench_traversal_test.go:715: 	ClosestNodes=2
+bench_traversal_test.go:715: 	ClosestNodes=2
+bench_traversal_test.go:715: 	ClosestNodes=2
+ok  	github.com/PuerkitoBio/goquery	192.541s

+ 420 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.2.0-v0.2.1-go1.1rc1.svg

@@ -0,0 +1,420 @@
+<?xml version="1.0"?>
+<!-- Generated by SVGo -->
+<svg width="1024" height="768"
+     xmlns="http://www.w3.org/2000/svg" 
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+<g style="font-size:20px;font-family:sans-serif">
+<rect x="0" y="0" width="1024" height="768" style="fill:white"/>
+<text x="100" y="50" style="font-size:150%"></text>
+<g style="font-style:italic;font-size:75%">
+<rect x="606" y="118" width="6" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="601" y="133" style="text-anchor:end">+2.12%</text>
+</g>
+<text x="100" y="138" style="text-anchor:start">BenchmarkFirst</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="609" y="144" width="3" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="604" y="159" style="text-anchor:end">+1.06%</text>
+</g>
+<text x="100" y="164" style="text-anchor:start">BenchmarkLast</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="602" y="170" width="10" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="597" y="185" style="text-anchor:end">+3.63%</text>
+</g>
+<text x="100" y="190" style="text-anchor:start">BenchmarkEq</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="604" y="196" width="8" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="599" y="211" style="text-anchor:end">+2.67%</text>
+</g>
+<text x="100" y="216" style="text-anchor:start">BenchmarkSlice</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="222" width="72" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="689" y="237" style="text-anchor:start">-24.26%</text>
+</g>
+<text x="100" y="242" style="text-anchor:start">BenchmarkGet</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="248" width="125" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="742" y="263" style="text-anchor:start">-41.71%</text>
+</g>
+<text x="100" y="268" style="text-anchor:start">BenchmarkIndex</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="274" width="151" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="768" y="289" style="text-anchor:start">-50.42%</text>
+</g>
+<text x="100" y="294" style="text-anchor:start">BenchmarkIndexSelector</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="595" y="300" width="17" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="590" y="315" style="text-anchor:end">+5.94%</text>
+</g>
+<text x="100" y="320" style="text-anchor:start">BenchmarkIndexOfNode</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="588" y="326" width="24" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="583" y="341" style="text-anchor:end">+8.26%</text>
+</g>
+<text x="100" y="346" style="text-anchor:start">BenchmarkIndexOfSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="352" width="98" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="715" y="367" style="text-anchor:start">-32.94%</text>
+</g>
+<text x="100" y="372" style="text-anchor:start">BenchmarkMetalReviewExample</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="378" width="170" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="787" y="393" style="text-anchor:start">-56.84%</text>
+</g>
+<text x="100" y="398" style="text-anchor:start">BenchmarkAdd</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="593" y="404" width="19" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="588" y="419" style="text-anchor:end">+6.63%</text>
+</g>
+<text x="100" y="424" style="text-anchor:start">BenchmarkAddSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="584" y="430" width="28" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="579" y="445" style="text-anchor:end">+9.42%</text>
+</g>
+<text x="100" y="450" style="text-anchor:start">BenchmarkAddNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="604" y="456" width="8" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="599" y="471" style="text-anchor:end">+2.93%</text>
+</g>
+<text x="100" y="476" style="text-anchor:start">BenchmarkAndSelf</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="482" width="41" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="658" y="497" style="text-anchor:start">-13.75%</text>
+</g>
+<text x="100" y="502" style="text-anchor:start">BenchmarkFilter</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="508" width="44" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="661" y="523" style="text-anchor:start">-14.90%</text>
+</g>
+<text x="100" y="528" style="text-anchor:start">BenchmarkNot</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="609" y="534" width="3" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="604" y="549" style="text-anchor:end">+1.07%</text>
+</g>
+<text x="100" y="554" style="text-anchor:start">BenchmarkFilterFunction</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="598" y="560" width="14" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="593" y="575" style="text-anchor:end">+4.91%</text>
+</g>
+<text x="100" y="580" style="text-anchor:start">BenchmarkNotFunction</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="609" y="586" width="3" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="604" y="601" style="text-anchor:end">+1.03%</text>
+</g>
+<text x="100" y="606" style="text-anchor:start">BenchmarkFilterNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="600" y="612" width="12" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="595" y="627" style="text-anchor:end">+4.22%</text>
+</g>
+<text x="100" y="632" style="text-anchor:start">BenchmarkNotNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="638" width="3" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="620" y="653" style="text-anchor:start">-1.00%</text>
+</g>
+<text x="100" y="658" style="text-anchor:start">BenchmarkFilterSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="600" y="664" width="12" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="595" y="679" style="text-anchor:end">+4.00%</text>
+</g>
+<text x="100" y="684" style="text-anchor:start">BenchmarkNotSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="690" width="96" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="713" y="705" style="text-anchor:start">-32.12%</text>
+</g>
+<text x="100" y="710" style="text-anchor:start">BenchmarkHas</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="608" y="716" width="4" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="603" y="731" style="text-anchor:end">+1.59%</text>
+</g>
+<text x="100" y="736" style="text-anchor:start">BenchmarkHasNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="608" y="742" width="4" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="603" y="757" style="text-anchor:end">+1.56%</text>
+</g>
+<text x="100" y="762" style="text-anchor:start">BenchmarkHasSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="566" y="768" width="46" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="561" y="783" style="text-anchor:end">+15.54%</text>
+</g>
+<text x="100" y="788" style="text-anchor:start">BenchmarkEnd</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="607" y="794" width="5" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="602" y="809" style="text-anchor:end">+1.77%</text>
+</g>
+<text x="100" y="814" style="text-anchor:start">BenchmarkEach</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="820" width="10" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="627" y="835" style="text-anchor:start">-3.61%</text>
+</g>
+<text x="100" y="840" style="text-anchor:start">BenchmarkMap</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="846" width="49" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="666" y="861" style="text-anchor:start">-16.48%</text>
+</g>
+<text x="100" y="866" style="text-anchor:start">BenchmarkAttr</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="609" y="872" width="3" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="604" y="887" style="text-anchor:end">+1.33%</text>
+</g>
+<text x="100" y="892" style="text-anchor:start">BenchmarkText</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="898" width="177" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="794" y="913" style="text-anchor:start">-59.21%</text>
+</g>
+<text x="100" y="918" style="text-anchor:start">BenchmarkLength</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="924" width="27" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="644" y="939" style="text-anchor:start">-9.01%</text>
+</g>
+<text x="100" y="944" style="text-anchor:start">BenchmarkHtml</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="950" width="50" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="667" y="965" style="text-anchor:start">-16.73%</text>
+</g>
+<text x="100" y="970" style="text-anchor:start">BenchmarkIs</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="976" width="75" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="692" y="991" style="text-anchor:start">-25.08%</text>
+</g>
+<text x="100" y="996" style="text-anchor:start">BenchmarkIsPositional</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1002" width="34" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="651" y="1017" style="text-anchor:start">-11.55%</text>
+</g>
+<text x="100" y="1022" style="text-anchor:start">BenchmarkIsFunction</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="609" y="1028" width="3" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="604" y="1043" style="text-anchor:end">+1.28%</text>
+</g>
+<text x="100" y="1048" style="text-anchor:start">BenchmarkIsSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="606" y="1054" width="6" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="601" y="1069" style="text-anchor:end">+2.10%</text>
+</g>
+<text x="100" y="1074" style="text-anchor:start">BenchmarkIsNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1080" width="47" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="664" y="1095" style="text-anchor:start">-15.99%</text>
+</g>
+<text x="100" y="1100" style="text-anchor:start">BenchmarkHasClass</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1106" width="7" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="624" y="1121" style="text-anchor:start">-2.65%</text>
+</g>
+<text x="100" y="1126" style="text-anchor:start">BenchmarkContains</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1132" width="145" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="762" y="1147" style="text-anchor:start">-48.54%</text>
+</g>
+<text x="100" y="1152" style="text-anchor:start">BenchmarkFind</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1158" width="128" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="745" y="1173" style="text-anchor:start">-42.71%</text>
+</g>
+<text x="100" y="1178" style="text-anchor:start">BenchmarkFindWithinSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="526" y="1184" width="86" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="521" y="1199" style="text-anchor:end">+28.77%</text>
+</g>
+<text x="100" y="1204" style="text-anchor:start">BenchmarkFindSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="527" y="1210" width="85" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="522" y="1225" style="text-anchor:end">+28.47%</text>
+</g>
+<text x="100" y="1230" style="text-anchor:start">BenchmarkFindNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1236" width="188" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="805" y="1251" style="text-anchor:start">-62.84%</text>
+</g>
+<text x="100" y="1256" style="text-anchor:start">BenchmarkContents</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1262" width="174" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="791" y="1277" style="text-anchor:start">-58.06%</text>
+</g>
+<text x="100" y="1282" style="text-anchor:start">BenchmarkContentsFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1288" width="189" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="806" y="1303" style="text-anchor:start">-63.13%</text>
+</g>
+<text x="100" y="1308" style="text-anchor:start">BenchmarkChildren</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1314" width="114" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="731" y="1329" style="text-anchor:start">-38.24%</text>
+</g>
+<text x="100" y="1334" style="text-anchor:start">BenchmarkChildrenFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1340" width="10" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="627" y="1355" style="text-anchor:start">-3.66%</text>
+</g>
+<text x="100" y="1360" style="text-anchor:start">BenchmarkParent</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1366" width="18" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="635" y="1381" style="text-anchor:start">-6.06%</text>
+</g>
+<text x="100" y="1386" style="text-anchor:start">BenchmarkParentFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="602" y="1392" width="10" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="597" y="1407" style="text-anchor:end">+3.64%</text>
+</g>
+<text x="100" y="1412" style="text-anchor:start">BenchmarkParents</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="608" y="1418" width="4" height="20" style="fill-opacity:0.3;fill:red"/>
+<text x="603" y="1433" style="text-anchor:end">+1.44%</text>
+</g>
+<text x="100" y="1438" style="text-anchor:start">BenchmarkParentsFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1444" width="133" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="750" y="1459" style="text-anchor:start">-44.35%</text>
+</g>
+<text x="100" y="1464" style="text-anchor:start">BenchmarkParentsUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1470" width="95" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="712" y="1485" style="text-anchor:start">-31.99%</text>
+</g>
+<text x="100" y="1490" style="text-anchor:start">BenchmarkParentsUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1496" width="97" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="714" y="1511" style="text-anchor:start">-32.62%</text>
+</g>
+<text x="100" y="1516" style="text-anchor:start">BenchmarkParentsUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1522" width="137" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="754" y="1537" style="text-anchor:start">-45.83%</text>
+</g>
+<text x="100" y="1542" style="text-anchor:start">BenchmarkParentsFilteredUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1548" width="90" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="707" y="1563" style="text-anchor:start">-30.05%</text>
+</g>
+<text x="100" y="1568" style="text-anchor:start">BenchmarkParentsFilteredUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1574" width="89" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="706" y="1589" style="text-anchor:start">-29.97%</text>
+</g>
+<text x="100" y="1594" style="text-anchor:start">BenchmarkParentsFilteredUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1600" width="77" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="694" y="1615" style="text-anchor:start">-25.96%</text>
+</g>
+<text x="100" y="1620" style="text-anchor:start">BenchmarkSiblings</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1626" width="80" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="697" y="1641" style="text-anchor:start">-26.67%</text>
+</g>
+<text x="100" y="1646" style="text-anchor:start">BenchmarkSiblingsFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1652" width="199" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="816" y="1667" style="text-anchor:start">-66.40%</text>
+</g>
+<text x="100" y="1672" style="text-anchor:start">BenchmarkNext</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1678" width="193" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="810" y="1693" style="text-anchor:start">-64.46%</text>
+</g>
+<text x="100" y="1698" style="text-anchor:start">BenchmarkNextFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1704" width="79" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="696" y="1719" style="text-anchor:start">-26.50%</text>
+</g>
+<text x="100" y="1724" style="text-anchor:start">BenchmarkNextAll</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1730" width="78" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="695" y="1745" style="text-anchor:start">-26.27%</text>
+</g>
+<text x="100" y="1750" style="text-anchor:start">BenchmarkNextAllFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1756" width="200" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="817" y="1771" style="text-anchor:start">-66.84%</text>
+</g>
+<text x="100" y="1776" style="text-anchor:start">BenchmarkPrev</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1782" width="193" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="810" y="1797" style="text-anchor:start">-64.35%</text>
+</g>
+<text x="100" y="1802" style="text-anchor:start">BenchmarkPrevFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1808" width="120" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="737" y="1823" style="text-anchor:start">-40.05%</text>
+</g>
+<text x="100" y="1828" style="text-anchor:start">BenchmarkPrevAll</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1834" width="119" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="736" y="1849" style="text-anchor:start">-39.74%</text>
+</g>
+<text x="100" y="1854" style="text-anchor:start">BenchmarkPrevAllFiltered</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1860" width="155" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="772" y="1875" style="text-anchor:start">-51.73%</text>
+</g>
+<text x="100" y="1880" style="text-anchor:start">BenchmarkNextUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1886" width="100" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="717" y="1901" style="text-anchor:start">-33.42%</text>
+</g>
+<text x="100" y="1906" style="text-anchor:start">BenchmarkNextUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1912" width="121" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="738" y="1927" style="text-anchor:start">-40.42%</text>
+</g>
+<text x="100" y="1932" style="text-anchor:start">BenchmarkNextUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1938" width="140" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="757" y="1953" style="text-anchor:start">-46.87%</text>
+</g>
+<text x="100" y="1958" style="text-anchor:start">BenchmarkPrevUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1964" width="99" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="716" y="1979" style="text-anchor:start">-33.14%</text>
+</g>
+<text x="100" y="1984" style="text-anchor:start">BenchmarkPrevUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="1990" width="123" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="740" y="2005" style="text-anchor:start">-41.19%</text>
+</g>
+<text x="100" y="2010" style="text-anchor:start">BenchmarkPrevUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2016" width="167" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="784" y="2031" style="text-anchor:start">-55.92%</text>
+</g>
+<text x="100" y="2036" style="text-anchor:start">BenchmarkNextFilteredUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2042" width="97" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="714" y="2057" style="text-anchor:start">-32.49%</text>
+</g>
+<text x="100" y="2062" style="text-anchor:start">BenchmarkNextFilteredUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2068" width="99" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="716" y="2083" style="text-anchor:start">-33.00%</text>
+</g>
+<text x="100" y="2088" style="text-anchor:start">BenchmarkNextFilteredUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2094" width="175" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="792" y="2109" style="text-anchor:start">-58.54%</text>
+</g>
+<text x="100" y="2114" style="text-anchor:start">BenchmarkPrevFilteredUntil</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2120" width="101" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="718" y="2135" style="text-anchor:start">-33.80%</text>
+</g>
+<text x="100" y="2140" style="text-anchor:start">BenchmarkPrevFilteredUntilSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2146" width="100" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="717" y="2161" style="text-anchor:start">-33.44%</text>
+</g>
+<text x="100" y="2166" style="text-anchor:start">BenchmarkPrevFilteredUntilNodes</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2172" width="74" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="691" y="2187" style="text-anchor:start">-24.82%</text>
+</g>
+<text x="100" y="2192" style="text-anchor:start">BenchmarkClosest</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2198" width="103" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="720" y="2213" style="text-anchor:start">-34.45%</text>
+</g>
+<text x="100" y="2218" style="text-anchor:start">BenchmarkClosestSelection</text>
+<g style="font-style:italic;font-size:75%">
+<rect x="612" y="2224" width="105" height="20" style="fill-opacity:0.3;fill:green"/>
+<text x="722" y="2239" style="text-anchor:start">-35.30%</text>
+</g>
+<text x="100" y="2244" style="text-anchor:start">BenchmarkClosestNodes</text>
+</g>
+</svg>

+ 470 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.2.1-go1.1rc1

@@ -0,0 +1,470 @@
+PASS
+BenchmarkFirst	20000000	        96.3 ns/op
+BenchmarkLast	20000000	        95.7 ns/op
+BenchmarkEq	20000000	        97.1 ns/op
+BenchmarkSlice	20000000	        92.3 ns/op
+BenchmarkGet	1000000000	         2.06 ns/op
+BenchmarkIndex	 1000000	      1069 ns/op
+--- BENCH: BenchmarkIndex
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+BenchmarkIndexSelector	  100000	     26750 ns/op
+--- BENCH: BenchmarkIndexSelector
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+BenchmarkIndexOfNode	100000000	        10.7 ns/op
+--- BENCH: BenchmarkIndexOfNode
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+BenchmarkIndexOfSelection	100000000	        11.8 ns/op
+--- BENCH: BenchmarkIndexOfSelection
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+BenchmarkMetalReviewExample	   10000	    219078 ns/op
+--- BENCH: BenchmarkMetalReviewExample
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+		
+	bench_example_test.go:41: MetalReviewExample=10
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+	... [output truncated]
+BenchmarkAdd	  100000	     22345 ns/op
+--- BENCH: BenchmarkAdd
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+BenchmarkAddSelection	10000000	       209 ns/op
+--- BENCH: BenchmarkAddSelection
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+BenchmarkAddNodes	10000000	       209 ns/op
+--- BENCH: BenchmarkAddNodes
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+BenchmarkAndSelf	 1000000	      2568 ns/op
+--- BENCH: BenchmarkAndSelf
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+BenchmarkFilter	  100000	     26715 ns/op
+--- BENCH: BenchmarkFilter
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+BenchmarkNot	   50000	     30058 ns/op
+--- BENCH: BenchmarkNot
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+BenchmarkFilterFunction	   50000	     66346 ns/op
+--- BENCH: BenchmarkFilterFunction
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+BenchmarkNotFunction	   50000	     72646 ns/op
+--- BENCH: BenchmarkNotFunction
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+BenchmarkFilterNodes	   50000	     65493 ns/op
+--- BENCH: BenchmarkFilterNodes
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+BenchmarkNotNodes	   20000	     79466 ns/op
+--- BENCH: BenchmarkNotNodes
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+BenchmarkFilterSelection	   50000	     65494 ns/op
+--- BENCH: BenchmarkFilterSelection
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+BenchmarkNotSelection	   20000	     79387 ns/op
+--- BENCH: BenchmarkNotSelection
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+BenchmarkHas	    5000	    386571 ns/op
+--- BENCH: BenchmarkHas
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+BenchmarkHasNodes	   10000	    230664 ns/op
+--- BENCH: BenchmarkHasNodes
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+BenchmarkHasSelection	   10000	    230705 ns/op
+--- BENCH: BenchmarkHasSelection
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+BenchmarkEnd	500000000	         4.61 ns/op
+--- BENCH: BenchmarkEnd
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+BenchmarkEach	  200000	      9520 ns/op
+--- BENCH: BenchmarkEach
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+BenchmarkMap	  100000	     15960 ns/op
+--- BENCH: BenchmarkMap
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+BenchmarkAttr	50000000	        30.4 ns/op
+--- BENCH: BenchmarkAttr
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+BenchmarkText	  100000	     18718 ns/op
+BenchmarkLength	2000000000	         0.31 ns/op
+--- BENCH: BenchmarkLength
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+BenchmarkHtml	 5000000	       606 ns/op
+BenchmarkIs	  100000	     29289 ns/op
+--- BENCH: BenchmarkIs
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+BenchmarkIsPositional	  100000	     23834 ns/op
+--- BENCH: BenchmarkIsPositional
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+BenchmarkIsFunction	 1000000	      2436 ns/op
+--- BENCH: BenchmarkIsFunction
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+BenchmarkIsSelection	   50000	     67106 ns/op
+--- BENCH: BenchmarkIsSelection
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+BenchmarkIsNodes	   50000	     66042 ns/op
+--- BENCH: BenchmarkIsNodes
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+BenchmarkHasClass	    5000	    565347 ns/op
+--- BENCH: BenchmarkHasClass
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+BenchmarkContains	100000000	        11.0 ns/op
+--- BENCH: BenchmarkContains
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+BenchmarkFind	  100000	     27677 ns/op
+--- BENCH: BenchmarkFind
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+BenchmarkFindWithinSelection	   50000	     72162 ns/op
+--- BENCH: BenchmarkFindWithinSelection
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+BenchmarkFindSelection	    5000	    460124 ns/op
+--- BENCH: BenchmarkFindSelection
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+BenchmarkFindNodes	    5000	    459390 ns/op
+--- BENCH: BenchmarkFindNodes
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+BenchmarkContents	  500000	      3395 ns/op
+--- BENCH: BenchmarkContents
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+BenchmarkContentsFiltered	  500000	      5193 ns/op
+--- BENCH: BenchmarkContentsFiltered
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+BenchmarkChildren	 5000000	       667 ns/op
+--- BENCH: BenchmarkChildren
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+BenchmarkChildrenFiltered	  500000	      3591 ns/op
+--- BENCH: BenchmarkChildrenFiltered
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+BenchmarkParent	   50000	     43168 ns/op
+--- BENCH: BenchmarkParent
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+BenchmarkParentFiltered	   50000	     45836 ns/op
+--- BENCH: BenchmarkParentFiltered
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+BenchmarkParents	   20000	     92348 ns/op
+--- BENCH: BenchmarkParents
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+BenchmarkParentsFiltered	   20000	     95306 ns/op
+--- BENCH: BenchmarkParentsFiltered
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+BenchmarkParentsUntil	   50000	     72782 ns/op
+--- BENCH: BenchmarkParentsUntil
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+BenchmarkParentsUntilSelection	   10000	    157639 ns/op
+--- BENCH: BenchmarkParentsUntilSelection
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+BenchmarkParentsUntilNodes	   10000	    157510 ns/op
+--- BENCH: BenchmarkParentsUntilNodes
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+BenchmarkParentsFilteredUntil	  100000	     16987 ns/op
+--- BENCH: BenchmarkParentsFilteredUntil
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+BenchmarkParentsFilteredUntilSelection	  100000	     21174 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilSelection
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+BenchmarkParentsFilteredUntilNodes	  100000	     21238 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilNodes
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+BenchmarkSiblings	   50000	     66536 ns/op
+--- BENCH: BenchmarkSiblings
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+BenchmarkSiblingsFiltered	   50000	     71822 ns/op
+--- BENCH: BenchmarkSiblingsFiltered
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+BenchmarkNext	  200000	     10745 ns/op
+--- BENCH: BenchmarkNext
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+BenchmarkNextFiltered	  200000	     12399 ns/op
+--- BENCH: BenchmarkNextFiltered
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+BenchmarkNextAll	   50000	     44640 ns/op
+--- BENCH: BenchmarkNextAll
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+BenchmarkNextAllFiltered	   50000	     49713 ns/op
+--- BENCH: BenchmarkNextAllFiltered
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+BenchmarkPrev	  200000	     10743 ns/op
+--- BENCH: BenchmarkPrev
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+BenchmarkPrevFiltered	  200000	     12456 ns/op
+--- BENCH: BenchmarkPrevFiltered
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+BenchmarkPrevAll	  100000	     17600 ns/op
+--- BENCH: BenchmarkPrevAll
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+BenchmarkPrevAllFiltered	  100000	     19460 ns/op
+--- BENCH: BenchmarkPrevAllFiltered
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+BenchmarkNextUntil	   20000	     92630 ns/op
+--- BENCH: BenchmarkNextUntil
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+BenchmarkNextUntilSelection	   50000	     61285 ns/op
+--- BENCH: BenchmarkNextUntilSelection
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+BenchmarkNextUntilNodes	  100000	     25859 ns/op
+--- BENCH: BenchmarkNextUntilNodes
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+BenchmarkPrevUntil	   10000	    230236 ns/op
+--- BENCH: BenchmarkPrevUntil
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+BenchmarkPrevUntilSelection	   20000	     77837 ns/op
+--- BENCH: BenchmarkPrevUntilSelection
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+BenchmarkPrevUntilNodes	  100000	     20784 ns/op
+--- BENCH: BenchmarkPrevUntilNodes
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+BenchmarkNextFilteredUntil	   50000	     46147 ns/op
+--- BENCH: BenchmarkNextFilteredUntil
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+BenchmarkNextFilteredUntilSelection	   50000	     67164 ns/op
+--- BENCH: BenchmarkNextFilteredUntilSelection
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+BenchmarkNextFilteredUntilNodes	   50000	     66628 ns/op
+--- BENCH: BenchmarkNextFilteredUntilNodes
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+BenchmarkPrevFilteredUntil	   50000	     46697 ns/op
+--- BENCH: BenchmarkPrevFilteredUntil
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+BenchmarkPrevFilteredUntilSelection	   50000	     68646 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilSelection
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+BenchmarkPrevFilteredUntilNodes	   50000	     68745 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilNodes
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+BenchmarkClosest	  500000	      4909 ns/op
+--- BENCH: BenchmarkClosest
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+BenchmarkClosestSelection	 5000000	       744 ns/op
+--- BENCH: BenchmarkClosestSelection
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+BenchmarkClosestNodes	 5000000	       733 ns/op
+--- BENCH: BenchmarkClosestNodes
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+ok  	github.com/PuerkitoBio/goquery	220.793s

+ 476 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.3.0

@@ -0,0 +1,476 @@
+PASS
+BenchmarkFirst	20000000	        95.5 ns/op
+BenchmarkLast	20000000	        94.9 ns/op
+BenchmarkEq	20000000	        95.7 ns/op
+BenchmarkSlice	20000000	        91.7 ns/op
+BenchmarkGet	1000000000	         2.05 ns/op
+BenchmarkIndex	 1000000	      1079 ns/op
+--- BENCH: BenchmarkIndex
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+BenchmarkIndexSelector	  100000	     26972 ns/op
+--- BENCH: BenchmarkIndexSelector
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+BenchmarkIndexOfNode	100000000	        10.8 ns/op
+--- BENCH: BenchmarkIndexOfNode
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+BenchmarkIndexOfSelection	100000000	        11.7 ns/op
+--- BENCH: BenchmarkIndexOfSelection
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+BenchmarkMetalReviewExample	   10000	    213800 ns/op
+--- BENCH: BenchmarkMetalReviewExample
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+		
+	bench_example_test.go:41: MetalReviewExample=10
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+	... [output truncated]
+BenchmarkAdd	  100000	     21811 ns/op
+--- BENCH: BenchmarkAdd
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+BenchmarkAddSelection	10000000	       205 ns/op
+--- BENCH: BenchmarkAddSelection
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+BenchmarkAddNodes	10000000	       202 ns/op
+--- BENCH: BenchmarkAddNodes
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+BenchmarkAndSelf	 1000000	      2467 ns/op
+--- BENCH: BenchmarkAndSelf
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+BenchmarkFilter	  100000	     25643 ns/op
+--- BENCH: BenchmarkFilter
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+BenchmarkNot	  100000	     29566 ns/op
+--- BENCH: BenchmarkNot
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+BenchmarkFilterFunction	   50000	     66894 ns/op
+--- BENCH: BenchmarkFilterFunction
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+BenchmarkNotFunction	   50000	     72183 ns/op
+--- BENCH: BenchmarkNotFunction
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+BenchmarkFilterNodes	   50000	     65516 ns/op
+--- BENCH: BenchmarkFilterNodes
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+BenchmarkNotNodes	   20000	     78880 ns/op
+--- BENCH: BenchmarkNotNodes
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+BenchmarkFilterSelection	   50000	     65232 ns/op
+--- BENCH: BenchmarkFilterSelection
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+BenchmarkNotSelection	   20000	     78813 ns/op
+--- BENCH: BenchmarkNotSelection
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+BenchmarkHas	    5000	    388834 ns/op
+--- BENCH: BenchmarkHas
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+BenchmarkHasNodes	   10000	    228552 ns/op
+--- BENCH: BenchmarkHasNodes
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+BenchmarkHasSelection	   10000	    228365 ns/op
+--- BENCH: BenchmarkHasSelection
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+BenchmarkEnd	500000000	         4.62 ns/op
+--- BENCH: BenchmarkEnd
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+BenchmarkEach	  200000	      9548 ns/op
+--- BENCH: BenchmarkEach
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+BenchmarkMap	  100000	     15900 ns/op
+--- BENCH: BenchmarkMap
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+BenchmarkEachWithBreak	 1000000	      1650 ns/op
+--- BENCH: BenchmarkEachWithBreak
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+BenchmarkAttr	50000000	        30.5 ns/op
+--- BENCH: BenchmarkAttr
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+BenchmarkText	  100000	     18873 ns/op
+BenchmarkLength	2000000000	         0.31 ns/op
+--- BENCH: BenchmarkLength
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+BenchmarkHtml	 5000000	       616 ns/op
+BenchmarkIs	  100000	     29499 ns/op
+--- BENCH: BenchmarkIs
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+BenchmarkIsPositional	  100000	     23733 ns/op
+--- BENCH: BenchmarkIsPositional
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+BenchmarkIsFunction	 1000000	      2404 ns/op
+--- BENCH: BenchmarkIsFunction
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+BenchmarkIsSelection	   50000	     65376 ns/op
+--- BENCH: BenchmarkIsSelection
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+BenchmarkIsNodes	   50000	     65322 ns/op
+--- BENCH: BenchmarkIsNodes
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+BenchmarkHasClass	    5000	    558933 ns/op
+--- BENCH: BenchmarkHasClass
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+BenchmarkContains	100000000	        11.1 ns/op
+--- BENCH: BenchmarkContains
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+BenchmarkFind	  100000	     27841 ns/op
+--- BENCH: BenchmarkFind
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+BenchmarkFindWithinSelection	   50000	     72096 ns/op
+--- BENCH: BenchmarkFindWithinSelection
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+BenchmarkFindSelection	    5000	    457349 ns/op
+--- BENCH: BenchmarkFindSelection
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+BenchmarkFindNodes	    5000	    459324 ns/op
+--- BENCH: BenchmarkFindNodes
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+BenchmarkContents	  500000	      3435 ns/op
+--- BENCH: BenchmarkContents
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+BenchmarkContentsFiltered	  500000	      5241 ns/op
+--- BENCH: BenchmarkContentsFiltered
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+BenchmarkChildren	 5000000	       667 ns/op
+--- BENCH: BenchmarkChildren
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+BenchmarkChildrenFiltered	  500000	      3639 ns/op
+--- BENCH: BenchmarkChildrenFiltered
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+BenchmarkParent	   50000	     44867 ns/op
+--- BENCH: BenchmarkParent
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+BenchmarkParentFiltered	   50000	     46476 ns/op
+--- BENCH: BenchmarkParentFiltered
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+BenchmarkParents	   20000	     92559 ns/op
+--- BENCH: BenchmarkParents
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+BenchmarkParentsFiltered	   20000	     96142 ns/op
+--- BENCH: BenchmarkParentsFiltered
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+BenchmarkParentsUntil	   50000	     73931 ns/op
+--- BENCH: BenchmarkParentsUntil
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+BenchmarkParentsUntilSelection	   10000	    159820 ns/op
+--- BENCH: BenchmarkParentsUntilSelection
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+BenchmarkParentsUntilNodes	   10000	    158811 ns/op
+--- BENCH: BenchmarkParentsUntilNodes
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+BenchmarkParentsFilteredUntil	  100000	     17203 ns/op
+--- BENCH: BenchmarkParentsFilteredUntil
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+BenchmarkParentsFilteredUntilSelection	  100000	     21358 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilSelection
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+BenchmarkParentsFilteredUntilNodes	  100000	     21338 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilNodes
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+BenchmarkSiblings	   50000	     66463 ns/op
+--- BENCH: BenchmarkSiblings
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+BenchmarkSiblingsFiltered	   50000	     72503 ns/op
+--- BENCH: BenchmarkSiblingsFiltered
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+BenchmarkNext	  200000	     10881 ns/op
+--- BENCH: BenchmarkNext
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+BenchmarkNextFiltered	  200000	     12588 ns/op
+--- BENCH: BenchmarkNextFiltered
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+BenchmarkNextAll	   50000	     45075 ns/op
+--- BENCH: BenchmarkNextAll
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+BenchmarkNextAllFiltered	   50000	     50455 ns/op
+--- BENCH: BenchmarkNextAllFiltered
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+BenchmarkPrev	  200000	     10933 ns/op
+--- BENCH: BenchmarkPrev
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+BenchmarkPrevFiltered	  200000	     12579 ns/op
+--- BENCH: BenchmarkPrevFiltered
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+BenchmarkPrevAll	  100000	     17751 ns/op
+--- BENCH: BenchmarkPrevAll
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+BenchmarkPrevAllFiltered	  100000	     19702 ns/op
+--- BENCH: BenchmarkPrevAllFiltered
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+BenchmarkNextUntil	   20000	     93586 ns/op
+--- BENCH: BenchmarkNextUntil
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+BenchmarkNextUntilSelection	   50000	     61155 ns/op
+--- BENCH: BenchmarkNextUntilSelection
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+BenchmarkNextUntilNodes	  100000	     25805 ns/op
+--- BENCH: BenchmarkNextUntilNodes
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+BenchmarkPrevUntil	   10000	    232225 ns/op
+--- BENCH: BenchmarkPrevUntil
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+BenchmarkPrevUntilSelection	   20000	     78316 ns/op
+--- BENCH: BenchmarkPrevUntilSelection
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+BenchmarkPrevUntilNodes	  100000	     20657 ns/op
+--- BENCH: BenchmarkPrevUntilNodes
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+BenchmarkNextFilteredUntil	   50000	     46567 ns/op
+--- BENCH: BenchmarkNextFilteredUntil
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+BenchmarkNextFilteredUntilSelection	   50000	     67227 ns/op
+--- BENCH: BenchmarkNextFilteredUntilSelection
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+BenchmarkNextFilteredUntilNodes	   50000	     66995 ns/op
+--- BENCH: BenchmarkNextFilteredUntilNodes
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+BenchmarkPrevFilteredUntil	   50000	     47361 ns/op
+--- BENCH: BenchmarkPrevFilteredUntil
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+BenchmarkPrevFilteredUntilSelection	   50000	     68802 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilSelection
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+BenchmarkPrevFilteredUntilNodes	   50000	     68928 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilNodes
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+BenchmarkClosest	  500000	      4922 ns/op
+--- BENCH: BenchmarkClosest
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+BenchmarkClosestSelection	 5000000	       738 ns/op
+--- BENCH: BenchmarkClosestSelection
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+BenchmarkClosestNodes	 5000000	       737 ns/op
+--- BENCH: BenchmarkClosestNodes
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+ok  	github.com/PuerkitoBio/goquery	224.003s

+ 478 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.3.2-go1.2

@@ -0,0 +1,478 @@
+PASS
+BenchmarkFirst	20000000	        88.4 ns/op
+BenchmarkLast	20000000	        88.2 ns/op
+BenchmarkEq	20000000	        87.4 ns/op
+BenchmarkSlice	20000000	        84.9 ns/op
+BenchmarkGet	2000000000	         1.99 ns/op
+BenchmarkIndex	 2000000	       906 ns/op
+--- BENCH: BenchmarkIndex
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+BenchmarkIndexSelector	  100000	     22276 ns/op
+--- BENCH: BenchmarkIndexSelector
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+BenchmarkIndexOfNode	200000000	         9.72 ns/op
+--- BENCH: BenchmarkIndexOfNode
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+BenchmarkIndexOfSelection	100000000	        10.4 ns/op
+--- BENCH: BenchmarkIndexOfSelection
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+BenchmarkMetalReviewExample	   10000	    199277 ns/op
+--- BENCH: BenchmarkMetalReviewExample
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+		
+	bench_example_test.go:41: MetalReviewExample=10
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+	... [output truncated]
+BenchmarkAdd	  100000	     18277 ns/op
+--- BENCH: BenchmarkAdd
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+BenchmarkAddSelection	10000000	       200 ns/op
+--- BENCH: BenchmarkAddSelection
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+BenchmarkAddNodes	10000000	       189 ns/op
+--- BENCH: BenchmarkAddNodes
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+BenchmarkAndSelf	 1000000	      2569 ns/op
+--- BENCH: BenchmarkAndSelf
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+BenchmarkFilter	  100000	     25195 ns/op
+--- BENCH: BenchmarkFilter
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+BenchmarkNot	  100000	     29003 ns/op
+--- BENCH: BenchmarkNot
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+BenchmarkFilterFunction	   50000	     60690 ns/op
+--- BENCH: BenchmarkFilterFunction
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+BenchmarkNotFunction	   50000	     66008 ns/op
+--- BENCH: BenchmarkNotFunction
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+BenchmarkFilterNodes	   50000	     59723 ns/op
+--- BENCH: BenchmarkFilterNodes
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+BenchmarkNotNodes	   50000	     72698 ns/op
+--- BENCH: BenchmarkNotNodes
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+BenchmarkFilterSelection	   50000	     59598 ns/op
+--- BENCH: BenchmarkFilterSelection
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+BenchmarkNotSelection	   50000	     72526 ns/op
+--- BENCH: BenchmarkNotSelection
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+BenchmarkHas	    5000	    367076 ns/op
+--- BENCH: BenchmarkHas
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+BenchmarkHasNodes	   10000	    219710 ns/op
+--- BENCH: BenchmarkHasNodes
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+BenchmarkHasSelection	   10000	    219105 ns/op
+--- BENCH: BenchmarkHasSelection
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+BenchmarkEnd	500000000	         4.58 ns/op
+--- BENCH: BenchmarkEnd
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+BenchmarkEach	  200000	      8615 ns/op
+--- BENCH: BenchmarkEach
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+BenchmarkMap	  200000	     14271 ns/op
+--- BENCH: BenchmarkMap
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+BenchmarkEachWithBreak	 1000000	      1497 ns/op
+--- BENCH: BenchmarkEachWithBreak
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+BenchmarkAttr	50000000	        30.9 ns/op
+--- BENCH: BenchmarkAttr
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+BenchmarkText	  200000	     13729 ns/op
+BenchmarkLength	2000000000	         0.31 ns/op
+--- BENCH: BenchmarkLength
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+BenchmarkHtml	 5000000	       537 ns/op
+BenchmarkIs	  100000	     28904 ns/op
+--- BENCH: BenchmarkIs
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+BenchmarkIsPositional	  100000	     23556 ns/op
+--- BENCH: BenchmarkIsPositional
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+BenchmarkIsFunction	 1000000	      2195 ns/op
+--- BENCH: BenchmarkIsFunction
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+BenchmarkIsSelection	   50000	     60100 ns/op
+--- BENCH: BenchmarkIsSelection
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+BenchmarkIsNodes	   50000	     59962 ns/op
+--- BENCH: BenchmarkIsNodes
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+BenchmarkHasClass	    5000	    388679 ns/op
+--- BENCH: BenchmarkHasClass
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+BenchmarkContains	100000000	        11.0 ns/op
+--- BENCH: BenchmarkContains
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+BenchmarkFind	  100000	     22779 ns/op
+--- BENCH: BenchmarkFind
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+BenchmarkFindWithinSelection	   50000	     62033 ns/op
+--- BENCH: BenchmarkFindWithinSelection
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+BenchmarkFindSelection	    5000	    446918 ns/op
+--- BENCH: BenchmarkFindSelection
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+BenchmarkFindNodes	    5000	    441753 ns/op
+--- BENCH: BenchmarkFindNodes
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+BenchmarkContents	 1000000	      2807 ns/op
+--- BENCH: BenchmarkContents
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+BenchmarkContentsFiltered	  500000	      4477 ns/op
+--- BENCH: BenchmarkContentsFiltered
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+BenchmarkChildren	 5000000	       548 ns/op
+--- BENCH: BenchmarkChildren
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+BenchmarkChildrenFiltered	  500000	      3304 ns/op
+--- BENCH: BenchmarkChildrenFiltered
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+BenchmarkParent	   50000	     38248 ns/op
+--- BENCH: BenchmarkParent
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+BenchmarkParentFiltered	   50000	     40677 ns/op
+--- BENCH: BenchmarkParentFiltered
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+BenchmarkParents	   20000	     83043 ns/op
+--- BENCH: BenchmarkParents
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+BenchmarkParentsFiltered	   20000	     85391 ns/op
+--- BENCH: BenchmarkParentsFiltered
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+BenchmarkParentsUntil	   50000	     65118 ns/op
+--- BENCH: BenchmarkParentsUntil
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+BenchmarkParentsUntilSelection	   10000	    144028 ns/op
+--- BENCH: BenchmarkParentsUntilSelection
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+BenchmarkParentsUntilNodes	   10000	    146713 ns/op
+--- BENCH: BenchmarkParentsUntilNodes
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+BenchmarkParentsFilteredUntil	  100000	     15113 ns/op
+--- BENCH: BenchmarkParentsFilteredUntil
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+BenchmarkParentsFilteredUntilSelection	  100000	     18881 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilSelection
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+BenchmarkParentsFilteredUntilNodes	  100000	     18926 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilNodes
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+BenchmarkSiblings	   50000	     63221 ns/op
+--- BENCH: BenchmarkSiblings
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+BenchmarkSiblingsFiltered	   50000	     69028 ns/op
+--- BENCH: BenchmarkSiblingsFiltered
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+BenchmarkNext	  200000	      9133 ns/op
+--- BENCH: BenchmarkNext
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+BenchmarkNextFiltered	  200000	     10601 ns/op
+--- BENCH: BenchmarkNextFiltered
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+BenchmarkNextAll	   50000	     43089 ns/op
+--- BENCH: BenchmarkNextAll
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+BenchmarkNextAllFiltered	   50000	     47867 ns/op
+--- BENCH: BenchmarkNextAllFiltered
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+BenchmarkPrev	  200000	      9104 ns/op
+--- BENCH: BenchmarkPrev
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+BenchmarkPrevFiltered	  200000	     10579 ns/op
+--- BENCH: BenchmarkPrevFiltered
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+BenchmarkPrevAll	  100000	     15185 ns/op
+--- BENCH: BenchmarkPrevAll
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+BenchmarkPrevAllFiltered	  100000	     17108 ns/op
+--- BENCH: BenchmarkPrevAllFiltered
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+BenchmarkNextUntil	   20000	     81087 ns/op
+--- BENCH: BenchmarkNextUntil
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+BenchmarkNextUntilSelection	   50000	     55831 ns/op
+--- BENCH: BenchmarkNextUntilSelection
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+BenchmarkNextUntilNodes	  100000	     23130 ns/op
+--- BENCH: BenchmarkNextUntilNodes
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+BenchmarkPrevUntil	   10000	    204673 ns/op
+--- BENCH: BenchmarkPrevUntil
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+BenchmarkPrevUntilSelection	   50000	     70965 ns/op
+--- BENCH: BenchmarkPrevUntilSelection
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+BenchmarkPrevUntilNodes	  100000	     18591 ns/op
+--- BENCH: BenchmarkPrevUntilNodes
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+BenchmarkNextFilteredUntil	   50000	     42004 ns/op
+--- BENCH: BenchmarkNextFilteredUntil
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+BenchmarkNextFilteredUntilSelection	   50000	     61953 ns/op
+--- BENCH: BenchmarkNextFilteredUntilSelection
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+BenchmarkNextFilteredUntilNodes	   50000	     62124 ns/op
+--- BENCH: BenchmarkNextFilteredUntilNodes
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+BenchmarkPrevFilteredUntil	   50000	     42861 ns/op
+--- BENCH: BenchmarkPrevFilteredUntil
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+BenchmarkPrevFilteredUntilSelection	   50000	     62451 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilSelection
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+BenchmarkPrevFilteredUntilNodes	   50000	     62631 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilNodes
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+BenchmarkClosest	  500000	      4684 ns/op
+--- BENCH: BenchmarkClosest
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+BenchmarkClosestSelection	 5000000	       622 ns/op
+--- BENCH: BenchmarkClosestSelection
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+BenchmarkClosestNodes	 5000000	       617 ns/op
+--- BENCH: BenchmarkClosestNodes
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+ok  	github.com/PuerkitoBio/goquery	218.724s

+ 477 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.3.2-go1.2-take2

@@ -0,0 +1,477 @@
+PASS
+BenchmarkFirst	20000000	        88.3 ns/op
+BenchmarkLast	20000000	        88.9 ns/op
+BenchmarkEq	20000000	        86.7 ns/op
+BenchmarkSlice	20000000	        84.1 ns/op
+BenchmarkGet	2000000000	         1.99 ns/op
+BenchmarkIndex	 2000000	       907 ns/op
+--- BENCH: BenchmarkIndex
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+BenchmarkIndexSelector	  200000	     13052 ns/op
+--- BENCH: BenchmarkIndexSelector
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+BenchmarkIndexOfNode	100000000	        10.5 ns/op
+--- BENCH: BenchmarkIndexOfNode
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+BenchmarkIndexOfSelection	100000000	        11.6 ns/op
+--- BENCH: BenchmarkIndexOfSelection
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+BenchmarkMetalReviewExample	   10000	    189556 ns/op
+--- BENCH: BenchmarkMetalReviewExample
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+		
+	bench_example_test.go:41: MetalReviewExample=10
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+	... [output truncated]
+BenchmarkAdd	  200000	     13714 ns/op
+--- BENCH: BenchmarkAdd
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+BenchmarkAddSelection	10000000	       200 ns/op
+--- BENCH: BenchmarkAddSelection
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+BenchmarkAddNodes	10000000	       186 ns/op
+--- BENCH: BenchmarkAddNodes
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+BenchmarkAndSelf	 1000000	      2532 ns/op
+--- BENCH: BenchmarkAndSelf
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+BenchmarkFilter	  100000	     25199 ns/op
+--- BENCH: BenchmarkFilter
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+BenchmarkNot	  100000	     29162 ns/op
+--- BENCH: BenchmarkNot
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+BenchmarkFilterFunction	   50000	     60733 ns/op
+--- BENCH: BenchmarkFilterFunction
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+BenchmarkNotFunction	   50000	     66124 ns/op
+--- BENCH: BenchmarkNotFunction
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+BenchmarkFilterNodes	   50000	     59489 ns/op
+--- BENCH: BenchmarkFilterNodes
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+BenchmarkNotNodes	   50000	     73623 ns/op
+--- BENCH: BenchmarkNotNodes
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+BenchmarkFilterSelection	   50000	     60053 ns/op
+--- BENCH: BenchmarkFilterSelection
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+BenchmarkNotSelection	   50000	     73477 ns/op
+--- BENCH: BenchmarkNotSelection
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+BenchmarkHas	    5000	    364859 ns/op
+--- BENCH: BenchmarkHas
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+BenchmarkHasNodes	   10000	    226980 ns/op
+--- BENCH: BenchmarkHasNodes
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+BenchmarkHasSelection	   10000	    220471 ns/op
+--- BENCH: BenchmarkHasSelection
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+BenchmarkEnd	500000000	         4.64 ns/op
+--- BENCH: BenchmarkEnd
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+BenchmarkEach	  200000	      8811 ns/op
+--- BENCH: BenchmarkEach
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+BenchmarkMap	  100000	     15365 ns/op
+--- BENCH: BenchmarkMap
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+BenchmarkEachWithBreak	 1000000	      1559 ns/op
+--- BENCH: BenchmarkEachWithBreak
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+BenchmarkAttr	50000000	        31.7 ns/op
+--- BENCH: BenchmarkAttr
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+BenchmarkText	  200000	     13901 ns/op
+BenchmarkLength	2000000000	         0.31 ns/op
+--- BENCH: BenchmarkLength
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+BenchmarkHtml	 5000000	       541 ns/op
+BenchmarkIs	  100000	     29435 ns/op
+--- BENCH: BenchmarkIs
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+BenchmarkIsPositional	  100000	     22938 ns/op
+--- BENCH: BenchmarkIsPositional
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+BenchmarkIsFunction	 1000000	      2185 ns/op
+--- BENCH: BenchmarkIsFunction
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+BenchmarkIsSelection	   50000	     60607 ns/op
+--- BENCH: BenchmarkIsSelection
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+BenchmarkIsNodes	   50000	     61599 ns/op
+--- BENCH: BenchmarkIsNodes
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+BenchmarkHasClass	    5000	    395436 ns/op
+--- BENCH: BenchmarkHasClass
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+BenchmarkContains	100000000	        11.0 ns/op
+--- BENCH: BenchmarkContains
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+BenchmarkFind	  200000	     13788 ns/op
+--- BENCH: BenchmarkFind
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+BenchmarkFindWithinSelection	   50000	     54253 ns/op
+--- BENCH: BenchmarkFindWithinSelection
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+BenchmarkFindSelection	    5000	    438879 ns/op
+--- BENCH: BenchmarkFindSelection
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+BenchmarkFindNodes	    5000	    437225 ns/op
+--- BENCH: BenchmarkFindNodes
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+BenchmarkContents	 1000000	      2844 ns/op
+--- BENCH: BenchmarkContents
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+BenchmarkContentsFiltered	  500000	      4528 ns/op
+--- BENCH: BenchmarkContentsFiltered
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+BenchmarkChildren	 5000000	       552 ns/op
+--- BENCH: BenchmarkChildren
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+BenchmarkChildrenFiltered	  500000	      3345 ns/op
+--- BENCH: BenchmarkChildrenFiltered
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+BenchmarkParent	   50000	     39482 ns/op
+--- BENCH: BenchmarkParent
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+BenchmarkParentFiltered	   50000	     42113 ns/op
+--- BENCH: BenchmarkParentFiltered
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+BenchmarkParents	   20000	     84136 ns/op
+--- BENCH: BenchmarkParents
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+BenchmarkParentsFiltered	   20000	     86041 ns/op
+--- BENCH: BenchmarkParentsFiltered
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+BenchmarkParentsUntil	   50000	     65844 ns/op
+--- BENCH: BenchmarkParentsUntil
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+BenchmarkParentsUntilSelection	   10000	    146903 ns/op
+--- BENCH: BenchmarkParentsUntilSelection
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+BenchmarkParentsUntilNodes	   10000	    146638 ns/op
+--- BENCH: BenchmarkParentsUntilNodes
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+BenchmarkParentsFilteredUntil	  100000	     16413 ns/op
+--- BENCH: BenchmarkParentsFilteredUntil
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+BenchmarkParentsFilteredUntilSelection	  100000	     20366 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilSelection
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+BenchmarkParentsFilteredUntilNodes	  100000	     18800 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilNodes
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+BenchmarkSiblings	   50000	     63443 ns/op
+--- BENCH: BenchmarkSiblings
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+BenchmarkSiblingsFiltered	   50000	     69250 ns/op
+--- BENCH: BenchmarkSiblingsFiltered
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+BenchmarkNext	  200000	      9193 ns/op
+--- BENCH: BenchmarkNext
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+BenchmarkNextFiltered	  200000	     10767 ns/op
+--- BENCH: BenchmarkNextFiltered
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+BenchmarkNextAll	   50000	     42829 ns/op
+--- BENCH: BenchmarkNextAll
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+BenchmarkNextAllFiltered	   50000	     48174 ns/op
+--- BENCH: BenchmarkNextAllFiltered
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+BenchmarkPrev	  200000	      9114 ns/op
+--- BENCH: BenchmarkPrev
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+BenchmarkPrevFiltered	  200000	     11114 ns/op
+--- BENCH: BenchmarkPrevFiltered
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+BenchmarkPrevAll	  100000	     16387 ns/op
+--- BENCH: BenchmarkPrevAll
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+BenchmarkPrevAllFiltered	  100000	     18322 ns/op
+--- BENCH: BenchmarkPrevAllFiltered
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+BenchmarkNextUntil	   20000	     83828 ns/op
+--- BENCH: BenchmarkNextUntil
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+BenchmarkNextUntilSelection	   50000	     58822 ns/op
+--- BENCH: BenchmarkNextUntilSelection
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+BenchmarkNextUntilNodes	  100000	     23173 ns/op
+--- BENCH: BenchmarkNextUntilNodes
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+BenchmarkPrevUntil	   10000	    219407 ns/op
+--- BENCH: BenchmarkPrevUntil
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+BenchmarkPrevUntilSelection	   20000	     76033 ns/op
+--- BENCH: BenchmarkPrevUntilSelection
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+BenchmarkPrevUntilNodes	  100000	     19417 ns/op
+--- BENCH: BenchmarkPrevUntilNodes
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+BenchmarkNextFilteredUntil	   50000	     44648 ns/op
+--- BENCH: BenchmarkNextFilteredUntil
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+BenchmarkNextFilteredUntilSelection	   50000	     62751 ns/op
+--- BENCH: BenchmarkNextFilteredUntilSelection
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+BenchmarkNextFilteredUntilNodes	   50000	     62035 ns/op
+--- BENCH: BenchmarkNextFilteredUntilNodes
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+BenchmarkPrevFilteredUntil	   50000	     43331 ns/op
+--- BENCH: BenchmarkPrevFilteredUntil
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+BenchmarkPrevFilteredUntilSelection	   50000	     64767 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilSelection
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+BenchmarkPrevFilteredUntilNodes	   50000	     67808 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilNodes
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+BenchmarkClosest	  500000	      4870 ns/op
+--- BENCH: BenchmarkClosest
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+BenchmarkClosestSelection	 5000000	       656 ns/op
+--- BENCH: BenchmarkClosestSelection
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+BenchmarkClosestNodes	 5000000	       663 ns/op
+--- BENCH: BenchmarkClosestNodes
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+ok  	github.com/PuerkitoBio/goquery	218.007s

+ 477 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v0.3.2-go1.2rc1

@@ -0,0 +1,477 @@
+PASS
+BenchmarkFirst	20000000	        91.0 ns/op
+BenchmarkLast	20000000	        90.5 ns/op
+BenchmarkEq	20000000	        90.2 ns/op
+BenchmarkSlice	20000000	        88.0 ns/op
+BenchmarkGet	1000000000	         2.04 ns/op
+BenchmarkIndex	 2000000	       935 ns/op
+--- BENCH: BenchmarkIndex
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+	bench_array_test.go:73: Index=3
+BenchmarkIndexSelector	  100000	     23613 ns/op
+--- BENCH: BenchmarkIndexSelector
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+	bench_array_test.go:85: IndexSelector=4
+BenchmarkIndexOfNode	100000000	        10.2 ns/op
+--- BENCH: BenchmarkIndexOfNode
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+	bench_array_test.go:99: IndexOfNode=2
+BenchmarkIndexOfSelection	100000000	        11.0 ns/op
+--- BENCH: BenchmarkIndexOfSelection
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+	bench_array_test.go:111: IndexOfSelection=2
+BenchmarkMetalReviewExample	   10000	    213843 ns/op
+--- BENCH: BenchmarkMetalReviewExample
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+		
+	bench_example_test.go:41: MetalReviewExample=10
+	bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5).
+		Review 1: Over Your Threshold - Facticity (6.0).
+		Review 2: Nuclear Death Terror - Chaos Reigns (7.5).
+		Review 3: Evoken - Atra Mors (9.5).
+	... [output truncated]
+BenchmarkAdd	  100000	     18671 ns/op
+--- BENCH: BenchmarkAdd
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+	bench_expand_test.go:20: Add=43
+BenchmarkAddSelection	10000000	       204 ns/op
+--- BENCH: BenchmarkAddSelection
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+	bench_expand_test.go:37: AddSelection=43
+BenchmarkAddNodes	10000000	       195 ns/op
+--- BENCH: BenchmarkAddNodes
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+	bench_expand_test.go:55: AddNodes=43
+BenchmarkAndSelf	 1000000	      2611 ns/op
+--- BENCH: BenchmarkAndSelf
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+	bench_expand_test.go:71: AndSelf=44
+BenchmarkFilter	  100000	     27571 ns/op
+--- BENCH: BenchmarkFilter
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+	bench_filter_test.go:20: Filter=13
+BenchmarkNot	   50000	     32006 ns/op
+--- BENCH: BenchmarkNot
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+	bench_filter_test.go:36: Not=371
+BenchmarkFilterFunction	   50000	     61388 ns/op
+--- BENCH: BenchmarkFilterFunction
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+	bench_filter_test.go:55: FilterFunction=112
+BenchmarkNotFunction	   50000	     66702 ns/op
+--- BENCH: BenchmarkNotFunction
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+	bench_filter_test.go:74: NotFunction=261
+BenchmarkFilterNodes	   50000	     59699 ns/op
+--- BENCH: BenchmarkFilterNodes
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+	bench_filter_test.go:92: FilterNodes=2
+BenchmarkNotNodes	   50000	     73248 ns/op
+--- BENCH: BenchmarkNotNodes
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+	bench_filter_test.go:110: NotNodes=360
+BenchmarkFilterSelection	   50000	     59242 ns/op
+--- BENCH: BenchmarkFilterSelection
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+	bench_filter_test.go:127: FilterSelection=2
+BenchmarkNotSelection	   50000	     73211 ns/op
+--- BENCH: BenchmarkNotSelection
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+	bench_filter_test.go:144: NotSelection=360
+BenchmarkHas	    5000	    395087 ns/op
+--- BENCH: BenchmarkHas
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+	bench_filter_test.go:160: Has=13
+BenchmarkHasNodes	   10000	    215849 ns/op
+--- BENCH: BenchmarkHasNodes
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+	bench_filter_test.go:178: HasNodes=15
+BenchmarkHasSelection	   10000	    215612 ns/op
+--- BENCH: BenchmarkHasSelection
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+	bench_filter_test.go:195: HasSelection=15
+BenchmarkEnd	500000000	         4.59 ns/op
+--- BENCH: BenchmarkEnd
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+	bench_filter_test.go:211: End=373
+BenchmarkEach	  200000	      8588 ns/op
+--- BENCH: BenchmarkEach
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+	bench_iteration_test.go:22: Each=59
+BenchmarkMap	  200000	     14444 ns/op
+--- BENCH: BenchmarkMap
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+	bench_iteration_test.go:41: Map=59
+BenchmarkEachWithBreak	 1000000	      1490 ns/op
+--- BENCH: BenchmarkEachWithBreak
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+	bench_iteration_test.go:61: Each=10
+BenchmarkAttr	50000000	        30.9 ns/op
+--- BENCH: BenchmarkAttr
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+	bench_property_test.go:16: Attr=firstHeading
+BenchmarkText	  200000	     14017 ns/op
+BenchmarkLength	2000000000	         0.31 ns/op
+--- BENCH: BenchmarkLength
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+	bench_property_test.go:37: Length=14
+BenchmarkHtml	 5000000	       577 ns/op
+BenchmarkIs	   50000	     31936 ns/op
+--- BENCH: BenchmarkIs
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+	bench_query_test.go:16: Is=true
+BenchmarkIsPositional	  100000	     23372 ns/op
+--- BENCH: BenchmarkIsPositional
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+	bench_query_test.go:28: IsPositional=true
+BenchmarkIsFunction	 1000000	      2170 ns/op
+--- BENCH: BenchmarkIsFunction
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+	bench_query_test.go:43: IsFunction=true
+BenchmarkIsSelection	   50000	     59814 ns/op
+--- BENCH: BenchmarkIsSelection
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+	bench_query_test.go:56: IsSelection=true
+BenchmarkIsNodes	   50000	     59629 ns/op
+--- BENCH: BenchmarkIsNodes
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+	bench_query_test.go:70: IsNodes=true
+BenchmarkHasClass	    5000	    384894 ns/op
+--- BENCH: BenchmarkHasClass
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+	bench_query_test.go:82: HasClass=true
+BenchmarkContains	100000000	        11.4 ns/op
+--- BENCH: BenchmarkContains
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+	bench_query_test.go:96: Contains=true
+BenchmarkFind	  100000	     23545 ns/op
+--- BENCH: BenchmarkFind
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+	bench_traversal_test.go:18: Find=41
+BenchmarkFindWithinSelection	   50000	     63775 ns/op
+--- BENCH: BenchmarkFindWithinSelection
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+	bench_traversal_test.go:34: FindWithinSelection=39
+BenchmarkFindSelection	    5000	    441958 ns/op
+--- BENCH: BenchmarkFindSelection
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+	bench_traversal_test.go:51: FindSelection=73
+BenchmarkFindNodes	    5000	    437717 ns/op
+--- BENCH: BenchmarkFindNodes
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+	bench_traversal_test.go:69: FindNodes=73
+BenchmarkContents	 1000000	      2799 ns/op
+--- BENCH: BenchmarkContents
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+	bench_traversal_test.go:85: Contents=16
+BenchmarkContentsFiltered	  500000	      4489 ns/op
+--- BENCH: BenchmarkContentsFiltered
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+	bench_traversal_test.go:101: ContentsFiltered=1
+BenchmarkChildren	 5000000	       546 ns/op
+--- BENCH: BenchmarkChildren
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+	bench_traversal_test.go:117: Children=2
+BenchmarkChildrenFiltered	  500000	      3472 ns/op
+--- BENCH: BenchmarkChildrenFiltered
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+	bench_traversal_test.go:133: ChildrenFiltered=2
+BenchmarkParent	   50000	     39067 ns/op
+--- BENCH: BenchmarkParent
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+	bench_traversal_test.go:149: Parent=55
+BenchmarkParentFiltered	   50000	     41450 ns/op
+--- BENCH: BenchmarkParentFiltered
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+	bench_traversal_test.go:165: ParentFiltered=4
+BenchmarkParents	   20000	     84864 ns/op
+--- BENCH: BenchmarkParents
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+	bench_traversal_test.go:181: Parents=73
+BenchmarkParentsFiltered	   20000	     87823 ns/op
+--- BENCH: BenchmarkParentsFiltered
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+	bench_traversal_test.go:197: ParentsFiltered=18
+BenchmarkParentsUntil	   50000	     65986 ns/op
+--- BENCH: BenchmarkParentsUntil
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+	bench_traversal_test.go:213: ParentsUntil=52
+BenchmarkParentsUntilSelection	   10000	    149798 ns/op
+--- BENCH: BenchmarkParentsUntilSelection
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+	bench_traversal_test.go:230: ParentsUntilSelection=70
+BenchmarkParentsUntilNodes	   10000	    148144 ns/op
+--- BENCH: BenchmarkParentsUntilNodes
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+	bench_traversal_test.go:248: ParentsUntilNodes=70
+BenchmarkParentsFilteredUntil	  100000	     15579 ns/op
+--- BENCH: BenchmarkParentsFilteredUntil
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+	bench_traversal_test.go:264: ParentsFilteredUntil=2
+BenchmarkParentsFilteredUntilSelection	  100000	     19094 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilSelection
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+	bench_traversal_test.go:281: ParentsFilteredUntilSelection=2
+BenchmarkParentsFilteredUntilNodes	  100000	     19037 ns/op
+--- BENCH: BenchmarkParentsFilteredUntilNodes
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+	bench_traversal_test.go:299: ParentsFilteredUntilNodes=2
+BenchmarkSiblings	   50000	     63891 ns/op
+--- BENCH: BenchmarkSiblings
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+	bench_traversal_test.go:315: Siblings=293
+BenchmarkSiblingsFiltered	   50000	     70424 ns/op
+--- BENCH: BenchmarkSiblingsFiltered
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+	bench_traversal_test.go:331: SiblingsFiltered=46
+BenchmarkNext	  200000	      9350 ns/op
+--- BENCH: BenchmarkNext
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+	bench_traversal_test.go:347: Next=49
+BenchmarkNextFiltered	  200000	     10929 ns/op
+--- BENCH: BenchmarkNextFiltered
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+	bench_traversal_test.go:363: NextFiltered=6
+BenchmarkNextAll	   50000	     43398 ns/op
+--- BENCH: BenchmarkNextAll
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+	bench_traversal_test.go:379: NextAll=234
+BenchmarkNextAllFiltered	   50000	     48519 ns/op
+--- BENCH: BenchmarkNextAllFiltered
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+	bench_traversal_test.go:395: NextAllFiltered=33
+BenchmarkPrev	  200000	      9181 ns/op
+--- BENCH: BenchmarkPrev
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+	bench_traversal_test.go:411: Prev=49
+BenchmarkPrevFiltered	  200000	     10811 ns/op
+--- BENCH: BenchmarkPrevFiltered
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+	bench_traversal_test.go:429: PrevFiltered=7
+BenchmarkPrevAll	  100000	     15589 ns/op
+--- BENCH: BenchmarkPrevAll
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+	bench_traversal_test.go:445: PrevAll=78
+BenchmarkPrevAllFiltered	  100000	     17341 ns/op
+--- BENCH: BenchmarkPrevAllFiltered
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+	bench_traversal_test.go:461: PrevAllFiltered=6
+BenchmarkNextUntil	   20000	     80663 ns/op
+--- BENCH: BenchmarkNextUntil
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+	bench_traversal_test.go:477: NextUntil=84
+BenchmarkNextUntilSelection	   50000	     56496 ns/op
+--- BENCH: BenchmarkNextUntilSelection
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+	bench_traversal_test.go:494: NextUntilSelection=42
+BenchmarkNextUntilNodes	  100000	     23729 ns/op
+--- BENCH: BenchmarkNextUntilNodes
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+	bench_traversal_test.go:512: NextUntilNodes=12
+BenchmarkPrevUntil	   10000	    208267 ns/op
+--- BENCH: BenchmarkPrevUntil
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+	bench_traversal_test.go:528: PrevUntil=238
+BenchmarkPrevUntilSelection	   50000	     72119 ns/op
+--- BENCH: BenchmarkPrevUntilSelection
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+	bench_traversal_test.go:545: PrevUntilSelection=49
+BenchmarkPrevUntilNodes	  100000	     18549 ns/op
+--- BENCH: BenchmarkPrevUntilNodes
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+	bench_traversal_test.go:563: PrevUntilNodes=11
+BenchmarkNextFilteredUntil	   50000	     42339 ns/op
+--- BENCH: BenchmarkNextFilteredUntil
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+	bench_traversal_test.go:579: NextFilteredUntil=22
+BenchmarkNextFilteredUntilSelection	   50000	     61916 ns/op
+--- BENCH: BenchmarkNextFilteredUntilSelection
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+	bench_traversal_test.go:596: NextFilteredUntilSelection=22
+BenchmarkNextFilteredUntilNodes	   50000	     62139 ns/op
+--- BENCH: BenchmarkNextFilteredUntilNodes
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+	bench_traversal_test.go:614: NextFilteredUntilNodes=22
+BenchmarkPrevFilteredUntil	   50000	     43409 ns/op
+--- BENCH: BenchmarkPrevFilteredUntil
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+	bench_traversal_test.go:630: PrevFilteredUntil=20
+BenchmarkPrevFilteredUntilSelection	   50000	     63768 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilSelection
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+	bench_traversal_test.go:647: PrevFilteredUntilSelection=20
+BenchmarkPrevFilteredUntilNodes	   50000	     63543 ns/op
+--- BENCH: BenchmarkPrevFilteredUntilNodes
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+	bench_traversal_test.go:665: PrevFilteredUntilNodes=20
+BenchmarkClosest	  500000	      5110 ns/op
+--- BENCH: BenchmarkClosest
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+	bench_traversal_test.go:681: Closest=2
+BenchmarkClosestSelection	 5000000	       629 ns/op
+--- BENCH: BenchmarkClosestSelection
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+	bench_traversal_test.go:698: ClosestSelection=2
+BenchmarkClosestNodes	 5000000	       627 ns/op
+--- BENCH: BenchmarkClosestNodes
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+	bench_traversal_test.go:715: ClosestNodes=2
+ok  	github.com/PuerkitoBio/goquery	215.785s

+ 85 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v1.0.0-go1.7

@@ -0,0 +1,85 @@
+BenchmarkFirst-4                           	30000000	        50.7 ns/op	      48 B/op	       1 allocs/op
+BenchmarkLast-4                            	30000000	        50.9 ns/op	      48 B/op	       1 allocs/op
+BenchmarkEq-4                              	30000000	        55.7 ns/op	      48 B/op	       1 allocs/op
+BenchmarkSlice-4                           	500000000	         3.45 ns/op	       0 B/op	       0 allocs/op
+BenchmarkGet-4                             	2000000000	         1.68 ns/op	       0 B/op	       0 allocs/op
+BenchmarkIndex-4                           	 3000000	       541 ns/op	     248 B/op	      10 allocs/op
+BenchmarkIndexSelector-4                   	  200000	     10749 ns/op	    2464 B/op	      17 allocs/op
+BenchmarkIndexOfNode-4                     	200000000	         6.47 ns/op	       0 B/op	       0 allocs/op
+BenchmarkIndexOfSelection-4                	200000000	         7.27 ns/op	       0 B/op	       0 allocs/op
+BenchmarkMetalReviewExample-4              	   10000	    138426 ns/op	   12240 B/op	     319 allocs/op
+BenchmarkAdd-4                             	  200000	     10192 ns/op	     208 B/op	       9 allocs/op
+BenchmarkAddSelection-4                    	10000000	       158 ns/op	      48 B/op	       1 allocs/op
+BenchmarkAddNodes-4                        	10000000	       156 ns/op	      48 B/op	       1 allocs/op
+BenchmarkAndSelf-4                         	 1000000	      1588 ns/op	    1008 B/op	       5 allocs/op
+BenchmarkFilter-4                          	  100000	     20427 ns/op	     360 B/op	       8 allocs/op
+BenchmarkNot-4                             	  100000	     23508 ns/op	     136 B/op	       5 allocs/op
+BenchmarkFilterFunction-4                  	   50000	     34178 ns/op	   22976 B/op	     755 allocs/op
+BenchmarkNotFunction-4                     	   50000	     38173 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkFilterNodes-4                     	   50000	     34001 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkNotNodes-4                        	   30000	     40344 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkFilterSelection-4                 	   50000	     33308 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkNotSelection-4                    	   30000	     40748 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkHas-4                             	    5000	    263346 ns/op	    1816 B/op	      48 allocs/op
+BenchmarkHasNodes-4                        	   10000	    160840 ns/op	   21184 B/op	     752 allocs/op
+BenchmarkHasSelection-4                    	   10000	    165410 ns/op	   21184 B/op	     752 allocs/op
+BenchmarkEnd-4                             	2000000000	         1.01 ns/op	       0 B/op	       0 allocs/op
+BenchmarkEach-4                            	  300000	      4664 ns/op	    3304 B/op	     118 allocs/op
+BenchmarkMap-4                             	  200000	      8286 ns/op	    5572 B/op	     184 allocs/op
+BenchmarkEachWithBreak-4                   	 2000000	       806 ns/op	     560 B/op	      20 allocs/op
+BenchmarkAttr-4                            	100000000	        21.6 ns/op	       0 B/op	       0 allocs/op
+BenchmarkText-4                            	  200000	      8909 ns/op	    7536 B/op	     110 allocs/op
+BenchmarkLength-4                          	2000000000	         0.34 ns/op	       0 B/op	       0 allocs/op
+BenchmarkHtml-4                            	 3000000	       422 ns/op	     120 B/op	       2 allocs/op
+BenchmarkIs-4                              	  100000	     22615 ns/op	      88 B/op	       4 allocs/op
+BenchmarkIsPositional-4                    	   50000	     26655 ns/op	    1112 B/op	      10 allocs/op
+BenchmarkIsFunction-4                      	 1000000	      1208 ns/op	     784 B/op	      28 allocs/op
+BenchmarkIsSelection-4                     	   50000	     33497 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkIsNodes-4                         	   50000	     33572 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkHasClass-4                        	   10000	    232802 ns/op	   14944 B/op	     976 allocs/op
+BenchmarkContains-4                        	200000000	         7.33 ns/op	       0 B/op	       0 allocs/op
+BenchmarkFind-4                            	  200000	     10715 ns/op	    2464 B/op	      17 allocs/op
+BenchmarkFindWithinSelection-4             	   50000	     35878 ns/op	    2176 B/op	      78 allocs/op
+BenchmarkFindSelection-4                   	   10000	    194356 ns/op	    2672 B/op	      82 allocs/op
+BenchmarkFindNodes-4                       	   10000	    195510 ns/op	    2672 B/op	      82 allocs/op
+BenchmarkContents-4                        	 1000000	      2252 ns/op	     864 B/op	      34 allocs/op
+BenchmarkContentsFiltered-4                	  500000	      3015 ns/op	    1016 B/op	      39 allocs/op
+BenchmarkChildren-4                        	 5000000	       364 ns/op	     152 B/op	       7 allocs/op
+BenchmarkChildrenFiltered-4                	 1000000	      2212 ns/op	     352 B/op	      15 allocs/op
+BenchmarkParent-4                          	   50000	     24643 ns/op	    4048 B/op	     381 allocs/op
+BenchmarkParentFiltered-4                  	   50000	     25967 ns/op	    4248 B/op	     388 allocs/op
+BenchmarkParents-4                         	   30000	     50000 ns/op	   27776 B/op	     830 allocs/op
+BenchmarkParentsFiltered-4                 	   30000	     53107 ns/op	   28360 B/op	     838 allocs/op
+BenchmarkParentsUntil-4                    	  100000	     22423 ns/op	   10352 B/op	     353 allocs/op
+BenchmarkParentsUntilSelection-4           	   20000	     86925 ns/op	   51144 B/op	    1516 allocs/op
+BenchmarkParentsUntilNodes-4               	   20000	     87597 ns/op	   51144 B/op	    1516 allocs/op
+BenchmarkParentsFilteredUntil-4            	  300000	      5568 ns/op	    2232 B/op	      86 allocs/op
+BenchmarkParentsFilteredUntilSelection-4   	  200000	     10966 ns/op	    5440 B/op	     190 allocs/op
+BenchmarkParentsFilteredUntilNodes-4       	  200000	     10919 ns/op	    5440 B/op	     190 allocs/op
+BenchmarkSiblings-4                        	   30000	     46018 ns/op	   15400 B/op	     204 allocs/op
+BenchmarkSiblingsFiltered-4                	   30000	     50566 ns/op	   16496 B/op	     213 allocs/op
+BenchmarkNext-4                            	  200000	      7921 ns/op	    3216 B/op	     112 allocs/op
+BenchmarkNextFiltered-4                    	  200000	      8804 ns/op	    3416 B/op	     118 allocs/op
+BenchmarkNextAll-4                         	   50000	     31098 ns/op	    9912 B/op	     138 allocs/op
+BenchmarkNextAllFiltered-4                 	   50000	     34677 ns/op	   11008 B/op	     147 allocs/op
+BenchmarkPrev-4                            	  200000	      7920 ns/op	    3216 B/op	     112 allocs/op
+BenchmarkPrevFiltered-4                    	  200000	      8913 ns/op	    3416 B/op	     118 allocs/op
+BenchmarkPrevAll-4                         	  200000	     10845 ns/op	    4376 B/op	     113 allocs/op
+BenchmarkPrevAllFiltered-4                 	  100000	     12030 ns/op	    4576 B/op	     119 allocs/op
+BenchmarkNextUntil-4                       	  100000	     19193 ns/op	    5760 B/op	     260 allocs/op
+BenchmarkNextUntilSelection-4              	   50000	     34829 ns/op	   18480 B/op	     542 allocs/op
+BenchmarkNextUntilNodes-4                  	  100000	     14459 ns/op	    7944 B/op	     248 allocs/op
+BenchmarkPrevUntil-4                       	   20000	     66296 ns/op	   12856 B/op	     448 allocs/op
+BenchmarkPrevUntilSelection-4              	   30000	     45037 ns/op	   23432 B/op	     689 allocs/op
+BenchmarkPrevUntilNodes-4                  	  200000	     11525 ns/op	    6152 B/op	     203 allocs/op
+BenchmarkNextFilteredUntil-4               	  100000	     12940 ns/op	    4512 B/op	     173 allocs/op
+BenchmarkNextFilteredUntilSelection-4      	   50000	     38924 ns/op	   19160 B/op	     567 allocs/op
+BenchmarkNextFilteredUntilNodes-4          	   50000	     38528 ns/op	   19160 B/op	     567 allocs/op
+BenchmarkPrevFilteredUntil-4               	  100000	     12980 ns/op	    4664 B/op	     175 allocs/op
+BenchmarkPrevFilteredUntilSelection-4      	   50000	     39671 ns/op	   19936 B/op	     587 allocs/op
+BenchmarkPrevFilteredUntilNodes-4          	   50000	     39484 ns/op	   19936 B/op	     587 allocs/op
+BenchmarkClosest-4                         	  500000	      3310 ns/op	     160 B/op	       8 allocs/op
+BenchmarkClosestSelection-4                	 5000000	       361 ns/op	      96 B/op	       6 allocs/op
+BenchmarkClosestNodes-4                    	 5000000	       359 ns/op	      96 B/op	       6 allocs/op
+PASS
+ok  	github.com/PuerkitoBio/goquery	163.718s

+ 85 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v1.0.1a-go1.7

@@ -0,0 +1,85 @@
+BenchmarkFirst-4                           	30000000	        50.9 ns/op	      48 B/op	       1 allocs/op
+BenchmarkLast-4                            	30000000	        50.0 ns/op	      48 B/op	       1 allocs/op
+BenchmarkEq-4                              	30000000	        50.5 ns/op	      48 B/op	       1 allocs/op
+BenchmarkSlice-4                           	500000000	         3.53 ns/op	       0 B/op	       0 allocs/op
+BenchmarkGet-4                             	2000000000	         1.66 ns/op	       0 B/op	       0 allocs/op
+BenchmarkIndex-4                           	 2000000	       832 ns/op	     248 B/op	      10 allocs/op
+BenchmarkIndexSelector-4                   	  100000	     16073 ns/op	    3839 B/op	      21 allocs/op
+BenchmarkIndexOfNode-4                     	200000000	         6.38 ns/op	       0 B/op	       0 allocs/op
+BenchmarkIndexOfSelection-4                	200000000	         7.14 ns/op	       0 B/op	       0 allocs/op
+BenchmarkMetalReviewExample-4              	   10000	    140737 ns/op	   12418 B/op	     320 allocs/op
+BenchmarkAdd-4                             	  100000	     13162 ns/op	     974 B/op	      10 allocs/op
+BenchmarkAddSelection-4                    	  500000	      3160 ns/op	     814 B/op	       2 allocs/op
+BenchmarkAddNodes-4                        	  500000	      3159 ns/op	     814 B/op	       2 allocs/op
+BenchmarkAndSelf-4                         	  200000	      7423 ns/op	    2404 B/op	       9 allocs/op
+BenchmarkFilter-4                          	  100000	     19671 ns/op	     360 B/op	       8 allocs/op
+BenchmarkNot-4                             	  100000	     22577 ns/op	     136 B/op	       5 allocs/op
+BenchmarkFilterFunction-4                  	   50000	     33960 ns/op	   22976 B/op	     755 allocs/op
+BenchmarkNotFunction-4                     	   50000	     37909 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkFilterNodes-4                     	   50000	     34196 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkNotNodes-4                        	   30000	     40446 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkFilterSelection-4                 	   50000	     33091 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkNotSelection-4                    	   30000	     40609 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkHas-4                             	    5000	    262936 ns/op	    2371 B/op	      50 allocs/op
+BenchmarkHasNodes-4                        	   10000	    148631 ns/op	   21184 B/op	     752 allocs/op
+BenchmarkHasSelection-4                    	   10000	    153117 ns/op	   21184 B/op	     752 allocs/op
+BenchmarkEnd-4                             	2000000000	         1.02 ns/op	       0 B/op	       0 allocs/op
+BenchmarkEach-4                            	  300000	      4653 ns/op	    3304 B/op	     118 allocs/op
+BenchmarkMap-4                             	  200000	      8257 ns/op	    5572 B/op	     184 allocs/op
+BenchmarkEachWithBreak-4                   	 2000000	       806 ns/op	     560 B/op	      20 allocs/op
+BenchmarkAttr-4                            	100000000	        22.0 ns/op	       0 B/op	       0 allocs/op
+BenchmarkText-4                            	  200000	      8913 ns/op	    7536 B/op	     110 allocs/op
+BenchmarkLength-4                          	2000000000	         0.35 ns/op	       0 B/op	       0 allocs/op
+BenchmarkHtml-4                            	 5000000	       398 ns/op	     120 B/op	       2 allocs/op
+BenchmarkIs-4                              	  100000	     22392 ns/op	      88 B/op	       4 allocs/op
+BenchmarkIsPositional-4                    	   50000	     26259 ns/op	    1112 B/op	      10 allocs/op
+BenchmarkIsFunction-4                      	 1000000	      1212 ns/op	     784 B/op	      28 allocs/op
+BenchmarkIsSelection-4                     	   50000	     33222 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkIsNodes-4                         	   50000	     33408 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkHasClass-4                        	   10000	    233208 ns/op	   14944 B/op	     976 allocs/op
+BenchmarkContains-4                        	200000000	         7.57 ns/op	       0 B/op	       0 allocs/op
+BenchmarkFind-4                            	  100000	     16121 ns/op	    3839 B/op	      21 allocs/op
+BenchmarkFindWithinSelection-4             	   20000	     68019 ns/op	   11521 B/op	      97 allocs/op
+BenchmarkFindSelection-4                   	    5000	    387582 ns/op	   59787 B/op	     176 allocs/op
+BenchmarkFindNodes-4                       	    5000	    389246 ns/op	   59797 B/op	     176 allocs/op
+BenchmarkContents-4                        	  200000	     11475 ns/op	    2878 B/op	      42 allocs/op
+BenchmarkContentsFiltered-4                	  200000	     11222 ns/op	    2498 B/op	      46 allocs/op
+BenchmarkChildren-4                        	 2000000	       650 ns/op	     152 B/op	       7 allocs/op
+BenchmarkChildrenFiltered-4                	  500000	      2568 ns/op	     352 B/op	      15 allocs/op
+BenchmarkParent-4                          	    2000	    702513 ns/op	  194478 B/op	     828 allocs/op
+BenchmarkParentFiltered-4                  	    2000	    690778 ns/op	  194658 B/op	     835 allocs/op
+BenchmarkParents-4                         	   10000	    124855 ns/op	   49869 B/op	     868 allocs/op
+BenchmarkParentsFiltered-4                 	   10000	    128535 ns/op	   50456 B/op	     876 allocs/op
+BenchmarkParentsUntil-4                    	   20000	     72982 ns/op	   23802 B/op	     388 allocs/op
+BenchmarkParentsUntilSelection-4           	   10000	    156099 ns/op	   72453 B/op	    1549 allocs/op
+BenchmarkParentsUntilNodes-4               	   10000	    156610 ns/op	   72455 B/op	    1549 allocs/op
+BenchmarkParentsFilteredUntil-4            	  100000	     15549 ns/op	    4068 B/op	      94 allocs/op
+BenchmarkParentsFilteredUntilSelection-4   	  100000	     20564 ns/op	    7276 B/op	     198 allocs/op
+BenchmarkParentsFilteredUntilNodes-4       	  100000	     20635 ns/op	    7276 B/op	     198 allocs/op
+BenchmarkSiblings-4                        	    3000	    565114 ns/op	  205910 B/op	     336 allocs/op
+BenchmarkSiblingsFiltered-4                	    3000	    580264 ns/op	  206993 B/op	     345 allocs/op
+BenchmarkNext-4                            	   20000	     93177 ns/op	   26810 B/op	     169 allocs/op
+BenchmarkNextFiltered-4                    	   20000	     94171 ns/op	   27013 B/op	     175 allocs/op
+BenchmarkNextAll-4                         	    5000	    270320 ns/op	   89289 B/op	     237 allocs/op
+BenchmarkNextAllFiltered-4                 	    5000	    275283 ns/op	   90375 B/op	     246 allocs/op
+BenchmarkPrev-4                            	   20000	     92777 ns/op	   26810 B/op	     169 allocs/op
+BenchmarkPrevFiltered-4                    	   20000	     95577 ns/op	   27007 B/op	     175 allocs/op
+BenchmarkPrevAll-4                         	   20000	     86339 ns/op	   27515 B/op	     151 allocs/op
+BenchmarkPrevAllFiltered-4                 	   20000	     87759 ns/op	   27715 B/op	     157 allocs/op
+BenchmarkNextUntil-4                       	   10000	    163930 ns/op	   48541 B/op	     330 allocs/op
+BenchmarkNextUntilSelection-4              	   30000	     56382 ns/op	   23880 B/op	     556 allocs/op
+BenchmarkNextUntilNodes-4                  	  100000	     18883 ns/op	    8703 B/op	     252 allocs/op
+BenchmarkPrevUntil-4                       	    3000	    484668 ns/op	  145402 B/op	     611 allocs/op
+BenchmarkPrevUntilSelection-4              	   20000	     72125 ns/op	   28865 B/op	     705 allocs/op
+BenchmarkPrevUntilNodes-4                  	  100000	     14722 ns/op	    6510 B/op	     205 allocs/op
+BenchmarkNextFilteredUntil-4               	   50000	     39006 ns/op	   10990 B/op	     192 allocs/op
+BenchmarkNextFilteredUntilSelection-4      	   20000	     66048 ns/op	   25641 B/op	     586 allocs/op
+BenchmarkNextFilteredUntilNodes-4          	   20000	     65314 ns/op	   25640 B/op	     586 allocs/op
+BenchmarkPrevFilteredUntil-4               	   50000	     33312 ns/op	    9709 B/op	     189 allocs/op
+BenchmarkPrevFilteredUntilSelection-4      	   20000	     64197 ns/op	   24981 B/op	     601 allocs/op
+BenchmarkPrevFilteredUntilNodes-4          	   20000	     64505 ns/op	   24982 B/op	     601 allocs/op
+BenchmarkClosest-4                         	  500000	      4065 ns/op	     160 B/op	       8 allocs/op
+BenchmarkClosestSelection-4                	 2000000	       756 ns/op	      96 B/op	       6 allocs/op
+BenchmarkClosestNodes-4                    	 2000000	       753 ns/op	      96 B/op	       6 allocs/op
+PASS
+ok  	github.com/PuerkitoBio/goquery	162.053s

+ 85 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v1.0.1b-go1.7

@@ -0,0 +1,85 @@
+BenchmarkFirst-4                           	30000000	        51.8 ns/op	      48 B/op	       1 allocs/op
+BenchmarkLast-4                            	30000000	        50.1 ns/op	      48 B/op	       1 allocs/op
+BenchmarkEq-4                              	30000000	        51.4 ns/op	      48 B/op	       1 allocs/op
+BenchmarkSlice-4                           	500000000	         3.52 ns/op	       0 B/op	       0 allocs/op
+BenchmarkGet-4                             	2000000000	         1.65 ns/op	       0 B/op	       0 allocs/op
+BenchmarkIndex-4                           	 2000000	       787 ns/op	     248 B/op	      10 allocs/op
+BenchmarkIndexSelector-4                   	  100000	     16952 ns/op	    3839 B/op	      21 allocs/op
+BenchmarkIndexOfNode-4                     	200000000	         6.42 ns/op	       0 B/op	       0 allocs/op
+BenchmarkIndexOfSelection-4                	200000000	         7.12 ns/op	       0 B/op	       0 allocs/op
+BenchmarkMetalReviewExample-4              	   10000	    141994 ns/op	   12418 B/op	     320 allocs/op
+BenchmarkAdd-4                             	  200000	     10367 ns/op	     208 B/op	       9 allocs/op
+BenchmarkAddSelection-4                    	10000000	       152 ns/op	      48 B/op	       1 allocs/op
+BenchmarkAddNodes-4                        	10000000	       147 ns/op	      48 B/op	       1 allocs/op
+BenchmarkAndSelf-4                         	 1000000	      1647 ns/op	    1008 B/op	       5 allocs/op
+BenchmarkFilter-4                          	  100000	     19522 ns/op	     360 B/op	       8 allocs/op
+BenchmarkNot-4                             	  100000	     22546 ns/op	     136 B/op	       5 allocs/op
+BenchmarkFilterFunction-4                  	   50000	     35087 ns/op	   22976 B/op	     755 allocs/op
+BenchmarkNotFunction-4                     	   50000	     39123 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkFilterNodes-4                     	   50000	     34890 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkNotNodes-4                        	   30000	     41145 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkFilterSelection-4                 	   50000	     33735 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkNotSelection-4                    	   30000	     41334 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkHas-4                             	    5000	    264058 ns/op	    2370 B/op	      50 allocs/op
+BenchmarkHasNodes-4                        	   10000	    151718 ns/op	   21184 B/op	     752 allocs/op
+BenchmarkHasSelection-4                    	   10000	    156955 ns/op	   21184 B/op	     752 allocs/op
+BenchmarkEnd-4                             	2000000000	         1.01 ns/op	       0 B/op	       0 allocs/op
+BenchmarkEach-4                            	  300000	      4660 ns/op	    3304 B/op	     118 allocs/op
+BenchmarkMap-4                             	  200000	      8404 ns/op	    5572 B/op	     184 allocs/op
+BenchmarkEachWithBreak-4                   	 2000000	       806 ns/op	     560 B/op	      20 allocs/op
+BenchmarkAttr-4                            	100000000	        21.6 ns/op	       0 B/op	       0 allocs/op
+BenchmarkText-4                            	  200000	      8911 ns/op	    7536 B/op	     110 allocs/op
+BenchmarkLength-4                          	2000000000	         0.34 ns/op	       0 B/op	       0 allocs/op
+BenchmarkHtml-4                            	 3000000	       405 ns/op	     120 B/op	       2 allocs/op
+BenchmarkIs-4                              	  100000	     22228 ns/op	      88 B/op	       4 allocs/op
+BenchmarkIsPositional-4                    	   50000	     26469 ns/op	    1112 B/op	      10 allocs/op
+BenchmarkIsFunction-4                      	 1000000	      1240 ns/op	     784 B/op	      28 allocs/op
+BenchmarkIsSelection-4                     	   50000	     33709 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkIsNodes-4                         	   50000	     33711 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkHasClass-4                        	   10000	    236005 ns/op	   14944 B/op	     976 allocs/op
+BenchmarkContains-4                        	200000000	         7.47 ns/op	       0 B/op	       0 allocs/op
+BenchmarkFind-4                            	  100000	     16075 ns/op	    3839 B/op	      21 allocs/op
+BenchmarkFindWithinSelection-4             	   30000	     41418 ns/op	    3539 B/op	      82 allocs/op
+BenchmarkFindSelection-4                   	   10000	    209490 ns/op	    5616 B/op	      89 allocs/op
+BenchmarkFindNodes-4                       	   10000	    208206 ns/op	    5614 B/op	      89 allocs/op
+BenchmarkContents-4                        	  300000	      4751 ns/op	    1420 B/op	      36 allocs/op
+BenchmarkContentsFiltered-4                	  300000	      5454 ns/op	    1570 B/op	      41 allocs/op
+BenchmarkChildren-4                        	 3000000	       527 ns/op	     152 B/op	       7 allocs/op
+BenchmarkChildrenFiltered-4                	 1000000	      2484 ns/op	     352 B/op	      15 allocs/op
+BenchmarkParent-4                          	   50000	     34724 ns/op	    6940 B/op	     387 allocs/op
+BenchmarkParentFiltered-4                  	   50000	     35596 ns/op	    7141 B/op	     394 allocs/op
+BenchmarkParents-4                         	   20000	     62094 ns/op	   30720 B/op	     837 allocs/op
+BenchmarkParentsFiltered-4                 	   20000	     63223 ns/op	   31304 B/op	     845 allocs/op
+BenchmarkParentsUntil-4                    	   50000	     30391 ns/op	   11828 B/op	     358 allocs/op
+BenchmarkParentsUntilSelection-4           	   20000	     99962 ns/op	   54075 B/op	    1523 allocs/op
+BenchmarkParentsUntilNodes-4               	   20000	     98763 ns/op	   54073 B/op	    1523 allocs/op
+BenchmarkParentsFilteredUntil-4            	  200000	      7982 ns/op	    2787 B/op	      88 allocs/op
+BenchmarkParentsFilteredUntilSelection-4   	  100000	     13618 ns/op	    5995 B/op	     192 allocs/op
+BenchmarkParentsFilteredUntilNodes-4       	  100000	     13639 ns/op	    5994 B/op	     192 allocs/op
+BenchmarkSiblings-4                        	   20000	     75287 ns/op	   28453 B/op	     225 allocs/op
+BenchmarkSiblingsFiltered-4                	   20000	     80139 ns/op	   29543 B/op	     234 allocs/op
+BenchmarkNext-4                            	  100000	     14270 ns/op	    4659 B/op	     117 allocs/op
+BenchmarkNextFiltered-4                    	  100000	     15352 ns/op	    4860 B/op	     123 allocs/op
+BenchmarkNextAll-4                         	   20000	     60811 ns/op	   22771 B/op	     157 allocs/op
+BenchmarkNextAllFiltered-4                 	   20000	     69079 ns/op	   23871 B/op	     166 allocs/op
+BenchmarkPrev-4                            	  100000	     14417 ns/op	    4659 B/op	     117 allocs/op
+BenchmarkPrevFiltered-4                    	  100000	     15443 ns/op	    4859 B/op	     123 allocs/op
+BenchmarkPrevAll-4                         	  100000	     22008 ns/op	    7346 B/op	     120 allocs/op
+BenchmarkPrevAllFiltered-4                 	  100000	     23212 ns/op	    7544 B/op	     126 allocs/op
+BenchmarkNextUntil-4                       	   50000	     30589 ns/op	    8767 B/op	     267 allocs/op
+BenchmarkNextUntilSelection-4              	   30000	     40875 ns/op	   19862 B/op	     546 allocs/op
+BenchmarkNextUntilNodes-4                  	  100000	     15987 ns/op	    8134 B/op	     249 allocs/op
+BenchmarkPrevUntil-4                       	   20000	     98799 ns/op	   25727 B/op	     467 allocs/op
+BenchmarkPrevUntilSelection-4              	   30000	     51874 ns/op	   24875 B/op	     694 allocs/op
+BenchmarkPrevUntilNodes-4                  	  100000	     12901 ns/op	    6334 B/op	     204 allocs/op
+BenchmarkNextFilteredUntil-4               	  100000	     19869 ns/op	    5909 B/op	     177 allocs/op
+BenchmarkNextFilteredUntilSelection-4      	   30000	     45412 ns/op	   20557 B/op	     571 allocs/op
+BenchmarkNextFilteredUntilNodes-4          	   30000	     45363 ns/op	   20557 B/op	     571 allocs/op
+BenchmarkPrevFilteredUntil-4               	  100000	     19357 ns/op	    6033 B/op	     179 allocs/op
+BenchmarkPrevFilteredUntilSelection-4      	   30000	     46396 ns/op	   21305 B/op	     591 allocs/op
+BenchmarkPrevFilteredUntilNodes-4          	   30000	     46133 ns/op	   21305 B/op	     591 allocs/op
+BenchmarkClosest-4                         	  500000	      3448 ns/op	     160 B/op	       8 allocs/op
+BenchmarkClosestSelection-4                	 3000000	       528 ns/op	      96 B/op	       6 allocs/op
+BenchmarkClosestNodes-4                    	 3000000	       523 ns/op	      96 B/op	       6 allocs/op
+PASS
+ok  	github.com/PuerkitoBio/goquery	162.012s

+ 86 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench/v1.0.1c-go1.7

@@ -0,0 +1,86 @@
+BenchmarkFirst-4                           	30000000	        51.7 ns/op	      48 B/op	       1 allocs/op
+BenchmarkLast-4                            	30000000	        51.9 ns/op	      48 B/op	       1 allocs/op
+BenchmarkEq-4                              	30000000	        50.0 ns/op	      48 B/op	       1 allocs/op
+BenchmarkSlice-4                           	500000000	         3.47 ns/op	       0 B/op	       0 allocs/op
+BenchmarkGet-4                             	2000000000	         1.68 ns/op	       0 B/op	       0 allocs/op
+BenchmarkIndex-4                           	 2000000	       804 ns/op	     248 B/op	      10 allocs/op
+BenchmarkIndexSelector-4                   	  100000	     16285 ns/op	    3839 B/op	      21 allocs/op
+BenchmarkIndexOfNode-4                     	200000000	         6.50 ns/op	       0 B/op	       0 allocs/op
+BenchmarkIndexOfSelection-4                	200000000	         7.02 ns/op	       0 B/op	       0 allocs/op
+BenchmarkMetalReviewExample-4              	   10000	    143160 ns/op	   12417 B/op	     320 allocs/op
+BenchmarkAdd-4                             	  200000	     10326 ns/op	     208 B/op	       9 allocs/op
+BenchmarkAddSelection-4                    	10000000	       155 ns/op	      48 B/op	       1 allocs/op
+BenchmarkAddNodes-4                        	10000000	       156 ns/op	      48 B/op	       1 allocs/op
+BenchmarkAddNodesBig-4                     	   20000	     94439 ns/op	   21847 B/op	      37 allocs/op
+BenchmarkAndSelf-4                         	 1000000	      1791 ns/op	    1008 B/op	       5 allocs/op
+BenchmarkFilter-4                          	  100000	     19470 ns/op	     360 B/op	       8 allocs/op
+BenchmarkNot-4                             	  100000	     22500 ns/op	     136 B/op	       5 allocs/op
+BenchmarkFilterFunction-4                  	   50000	     34578 ns/op	   22976 B/op	     755 allocs/op
+BenchmarkNotFunction-4                     	   50000	     38703 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkFilterNodes-4                     	   50000	     34486 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkNotNodes-4                        	   30000	     41094 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkFilterSelection-4                 	   50000	     33623 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkNotSelection-4                    	   30000	     41483 ns/op	   29120 B/op	     757 allocs/op
+BenchmarkHas-4                             	    5000	    266628 ns/op	    2371 B/op	      50 allocs/op
+BenchmarkHasNodes-4                        	   10000	    152617 ns/op	   21184 B/op	     752 allocs/op
+BenchmarkHasSelection-4                    	   10000	    156682 ns/op	   21184 B/op	     752 allocs/op
+BenchmarkEnd-4                             	2000000000	         1.00 ns/op	       0 B/op	       0 allocs/op
+BenchmarkEach-4                            	  300000	      4712 ns/op	    3304 B/op	     118 allocs/op
+BenchmarkMap-4                             	  200000	      8434 ns/op	    5572 B/op	     184 allocs/op
+BenchmarkEachWithBreak-4                   	 2000000	       819 ns/op	     560 B/op	      20 allocs/op
+BenchmarkAttr-4                            	100000000	        21.7 ns/op	       0 B/op	       0 allocs/op
+BenchmarkText-4                            	  200000	      9376 ns/op	    7536 B/op	     110 allocs/op
+BenchmarkLength-4                          	2000000000	         0.35 ns/op	       0 B/op	       0 allocs/op
+BenchmarkHtml-4                            	 5000000	       401 ns/op	     120 B/op	       2 allocs/op
+BenchmarkIs-4                              	  100000	     22214 ns/op	      88 B/op	       4 allocs/op
+BenchmarkIsPositional-4                    	   50000	     26559 ns/op	    1112 B/op	      10 allocs/op
+BenchmarkIsFunction-4                      	 1000000	      1228 ns/op	     784 B/op	      28 allocs/op
+BenchmarkIsSelection-4                     	   50000	     33471 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkIsNodes-4                         	   50000	     34461 ns/op	   20960 B/op	     749 allocs/op
+BenchmarkHasClass-4                        	   10000	    232429 ns/op	   14944 B/op	     976 allocs/op
+BenchmarkContains-4                        	200000000	         7.62 ns/op	       0 B/op	       0 allocs/op
+BenchmarkFind-4                            	  100000	     16114 ns/op	    3839 B/op	      21 allocs/op
+BenchmarkFindWithinSelection-4             	   30000	     42520 ns/op	    3540 B/op	      82 allocs/op
+BenchmarkFindSelection-4                   	   10000	    209801 ns/op	    5615 B/op	      89 allocs/op
+BenchmarkFindNodes-4                       	   10000	    209082 ns/op	    5614 B/op	      89 allocs/op
+BenchmarkContents-4                        	  300000	      4836 ns/op	    1420 B/op	      36 allocs/op
+BenchmarkContentsFiltered-4                	  200000	      5495 ns/op	    1570 B/op	      41 allocs/op
+BenchmarkChildren-4                        	 3000000	       527 ns/op	     152 B/op	       7 allocs/op
+BenchmarkChildrenFiltered-4                	  500000	      2499 ns/op	     352 B/op	      15 allocs/op
+BenchmarkParent-4                          	   50000	     34072 ns/op	    6942 B/op	     387 allocs/op
+BenchmarkParentFiltered-4                  	   50000	     36077 ns/op	    7141 B/op	     394 allocs/op
+BenchmarkParents-4                         	   20000	     64118 ns/op	   30719 B/op	     837 allocs/op
+BenchmarkParentsFiltered-4                 	   20000	     63432 ns/op	   31303 B/op	     845 allocs/op
+BenchmarkParentsUntil-4                    	   50000	     29589 ns/op	   11829 B/op	     358 allocs/op
+BenchmarkParentsUntilSelection-4           	   10000	    101033 ns/op	   54076 B/op	    1523 allocs/op
+BenchmarkParentsUntilNodes-4               	   10000	    100584 ns/op	   54076 B/op	    1523 allocs/op
+BenchmarkParentsFilteredUntil-4            	  200000	      8061 ns/op	    2787 B/op	      88 allocs/op
+BenchmarkParentsFilteredUntilSelection-4   	  100000	     13848 ns/op	    5995 B/op	     192 allocs/op
+BenchmarkParentsFilteredUntilNodes-4       	  100000	     13766 ns/op	    5995 B/op	     192 allocs/op
+BenchmarkSiblings-4                        	   20000	     75135 ns/op	   28453 B/op	     225 allocs/op
+BenchmarkSiblingsFiltered-4                	   20000	     80532 ns/op	   29544 B/op	     234 allocs/op
+BenchmarkNext-4                            	  100000	     14200 ns/op	    4660 B/op	     117 allocs/op
+BenchmarkNextFiltered-4                    	  100000	     15284 ns/op	    4859 B/op	     123 allocs/op
+BenchmarkNextAll-4                         	   20000	     60889 ns/op	   22774 B/op	     157 allocs/op
+BenchmarkNextAllFiltered-4                 	   20000	     65125 ns/op	   23869 B/op	     166 allocs/op
+BenchmarkPrev-4                            	  100000	     14448 ns/op	    4659 B/op	     117 allocs/op
+BenchmarkPrevFiltered-4                    	  100000	     15444 ns/op	    4859 B/op	     123 allocs/op
+BenchmarkPrevAll-4                         	  100000	     22019 ns/op	    7344 B/op	     120 allocs/op
+BenchmarkPrevAllFiltered-4                 	  100000	     23307 ns/op	    7545 B/op	     126 allocs/op
+BenchmarkNextUntil-4                       	   50000	     30287 ns/op	    8766 B/op	     267 allocs/op
+BenchmarkNextUntilSelection-4              	   30000	     41476 ns/op	   19862 B/op	     546 allocs/op
+BenchmarkNextUntilNodes-4                  	  100000	     16106 ns/op	    8133 B/op	     249 allocs/op
+BenchmarkPrevUntil-4                       	   20000	     98951 ns/op	   25728 B/op	     467 allocs/op
+BenchmarkPrevUntilSelection-4              	   30000	     52390 ns/op	   24875 B/op	     694 allocs/op
+BenchmarkPrevUntilNodes-4                  	  100000	     12986 ns/op	    6334 B/op	     204 allocs/op
+BenchmarkNextFilteredUntil-4               	  100000	     19365 ns/op	    5908 B/op	     177 allocs/op
+BenchmarkNextFilteredUntilSelection-4      	   30000	     45334 ns/op	   20555 B/op	     571 allocs/op
+BenchmarkNextFilteredUntilNodes-4          	   30000	     45292 ns/op	   20556 B/op	     571 allocs/op
+BenchmarkPrevFilteredUntil-4               	  100000	     19412 ns/op	    6032 B/op	     179 allocs/op
+BenchmarkPrevFilteredUntilSelection-4      	   30000	     46286 ns/op	   21304 B/op	     591 allocs/op
+BenchmarkPrevFilteredUntilNodes-4          	   30000	     46554 ns/op	   21305 B/op	     591 allocs/op
+BenchmarkClosest-4                         	  500000	      3480 ns/op	     160 B/op	       8 allocs/op
+BenchmarkClosestSelection-4                	 2000000	       722 ns/op	      96 B/op	       6 allocs/op
+BenchmarkClosestNodes-4                    	 2000000	       719 ns/op	      96 B/op	       6 allocs/op
+PASS
+ok  	github.com/PuerkitoBio/goquery	160.565s

+ 120 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench_array_test.go

@@ -0,0 +1,120 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func BenchmarkFirst(b *testing.B) {
+	b.StopTimer()
+	sel := DocB().Find("dd")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.First()
+	}
+}
+
+func BenchmarkLast(b *testing.B) {
+	b.StopTimer()
+	sel := DocB().Find("dd")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.Last()
+	}
+}
+
+func BenchmarkEq(b *testing.B) {
+	b.StopTimer()
+	sel := DocB().Find("dd")
+	j := 0
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.Eq(j)
+		if j++; j >= sel.Length() {
+			j = 0
+		}
+	}
+}
+
+func BenchmarkSlice(b *testing.B) {
+	b.StopTimer()
+	sel := DocB().Find("dd")
+	j := 0
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.Slice(j, j+4)
+		if j++; j >= (sel.Length() - 4) {
+			j = 0
+		}
+	}
+}
+
+func BenchmarkGet(b *testing.B) {
+	b.StopTimer()
+	sel := DocB().Find("dd")
+	j := 0
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.Get(j)
+		if j++; j >= sel.Length() {
+			j = 0
+		}
+	}
+}
+
+func BenchmarkIndex(b *testing.B) {
+	var j int
+
+	b.StopTimer()
+	sel := DocB().Find("#Main")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		j = sel.Index()
+	}
+	if j != 3 {
+		b.Fatalf("want 3, got %d", j)
+	}
+}
+
+func BenchmarkIndexSelector(b *testing.B) {
+	var j int
+
+	b.StopTimer()
+	sel := DocB().Find("#manual-nav dl dd:nth-child(1)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		j = sel.IndexSelector("dd")
+	}
+	if j != 4 {
+		b.Fatalf("want 4, got %d", j)
+	}
+}
+
+func BenchmarkIndexOfNode(b *testing.B) {
+	var j int
+
+	b.StopTimer()
+	sel := DocB().Find("span a")
+	sel2 := DocB().Find("span a:nth-child(3)")
+	n := sel2.Get(0)
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		j = sel.IndexOfNode(n)
+	}
+	if j != 2 {
+		b.Fatalf("want 2, got %d", j)
+	}
+}
+
+func BenchmarkIndexOfSelection(b *testing.B) {
+	var j int
+	b.StopTimer()
+	sel := DocB().Find("span a")
+	sel2 := DocB().Find("span a:nth-child(3)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		j = sel.IndexOfSelection(sel2)
+	}
+	if j != 2 {
+		b.Fatalf("want 2, got %d", j)
+	}
+}

+ 40 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench_example_test.go

@@ -0,0 +1,40 @@
+package goquery
+
+import (
+	"bytes"
+	"fmt"
+	"strconv"
+	"testing"
+)
+
+func BenchmarkMetalReviewExample(b *testing.B) {
+	var n int
+	var buf bytes.Buffer
+
+	b.StopTimer()
+	doc := loadDoc("metalreview.html")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		doc.Find(".slider-row:nth-child(1) .slider-item").Each(func(i int, s *Selection) {
+			var band, title string
+			var score float64
+			var e error
+
+			n++
+			// For each item found, get the band, title and score, and print it
+			band = s.Find("strong").Text()
+			title = s.Find("em").Text()
+			if score, e = strconv.ParseFloat(s.Find(".score").Text(), 64); e != nil {
+				// Not a valid float, ignore score
+				if n <= 4 {
+					buf.WriteString(fmt.Sprintf("Review %d: %s - %s.\n", i, band, title))
+				}
+			} else {
+				// Print all, including score
+				if n <= 4 {
+					buf.WriteString(fmt.Sprintf("Review %d: %s - %s (%2.1f).\n", i, band, title, score))
+				}
+			}
+		})
+	}
+}

+ 104 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench_expand_test.go

@@ -0,0 +1,104 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func BenchmarkAdd(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocB().Find("dd")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Add("h2[title]").Length()
+		} else {
+			sel.Add("h2[title]")
+		}
+	}
+	if n != 43 {
+		b.Fatalf("want 43, got %d", n)
+	}
+}
+
+func BenchmarkAddSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocB().Find("dd")
+	sel2 := DocB().Find("h2[title]")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.AddSelection(sel2).Length()
+		} else {
+			sel.AddSelection(sel2)
+		}
+	}
+	if n != 43 {
+		b.Fatalf("want 43, got %d", n)
+	}
+}
+
+func BenchmarkAddNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocB().Find("dd")
+	sel2 := DocB().Find("h2[title]")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.AddNodes(nodes...).Length()
+		} else {
+			sel.AddNodes(nodes...)
+		}
+	}
+	if n != 43 {
+		b.Fatalf("want 43, got %d", n)
+	}
+}
+
+func BenchmarkAddNodesBig(b *testing.B) {
+	var n int
+
+	doc := DocW()
+	sel := doc.Find("li")
+	// make nodes > 1000
+	nodes := sel.Nodes
+	nodes = append(nodes, nodes...)
+	nodes = append(nodes, nodes...)
+	sel = doc.Find("xyz")
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.AddNodes(nodes...).Length()
+		} else {
+			sel.AddNodes(nodes...)
+		}
+	}
+	if n != 373 {
+		b.Fatalf("want 373, got %d", n)
+	}
+}
+
+func BenchmarkAndSelf(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocB().Find("dd").Parent()
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.AndSelf().Length()
+		} else {
+			sel.AndSelf()
+		}
+	}
+	if n != 44 {
+		b.Fatalf("want 44, got %d", n)
+	}
+}

+ 236 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench_filter_test.go

@@ -0,0 +1,236 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func BenchmarkFilter(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Filter(".toclevel-1").Length()
+		} else {
+			sel.Filter(".toclevel-1")
+		}
+	}
+	if n != 13 {
+		b.Fatalf("want 13, got %d", n)
+	}
+}
+
+func BenchmarkNot(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Not(".toclevel-2").Length()
+		} else {
+			sel.Filter(".toclevel-2")
+		}
+	}
+	if n != 371 {
+		b.Fatalf("want 371, got %d", n)
+	}
+}
+
+func BenchmarkFilterFunction(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	f := func(i int, s *Selection) bool {
+		return len(s.Get(0).Attr) > 0
+	}
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.FilterFunction(f).Length()
+		} else {
+			sel.FilterFunction(f)
+		}
+	}
+	if n != 112 {
+		b.Fatalf("want 112, got %d", n)
+	}
+}
+
+func BenchmarkNotFunction(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	f := func(i int, s *Selection) bool {
+		return len(s.Get(0).Attr) > 0
+	}
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NotFunction(f).Length()
+		} else {
+			sel.NotFunction(f)
+		}
+	}
+	if n != 261 {
+		b.Fatalf("want 261, got %d", n)
+	}
+}
+
+func BenchmarkFilterNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	sel2 := DocW().Find(".toclevel-2")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.FilterNodes(nodes...).Length()
+		} else {
+			sel.FilterNodes(nodes...)
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkNotNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	sel2 := DocW().Find(".toclevel-1")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NotNodes(nodes...).Length()
+		} else {
+			sel.NotNodes(nodes...)
+		}
+	}
+	if n != 360 {
+		b.Fatalf("want 360, got %d", n)
+	}
+}
+
+func BenchmarkFilterSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	sel2 := DocW().Find(".toclevel-2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.FilterSelection(sel2).Length()
+		} else {
+			sel.FilterSelection(sel2)
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkNotSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	sel2 := DocW().Find(".toclevel-1")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NotSelection(sel2).Length()
+		} else {
+			sel.NotSelection(sel2)
+		}
+	}
+	if n != 360 {
+		b.Fatalf("want 360, got %d", n)
+	}
+}
+
+func BenchmarkHas(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Has(".editsection").Length()
+		} else {
+			sel.Has(".editsection")
+		}
+	}
+	if n != 13 {
+		b.Fatalf("want 13, got %d", n)
+	}
+}
+
+func BenchmarkHasNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	sel2 := DocW().Find(".tocnumber")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.HasNodes(nodes...).Length()
+		} else {
+			sel.HasNodes(nodes...)
+		}
+	}
+	if n != 15 {
+		b.Fatalf("want 15, got %d", n)
+	}
+}
+
+func BenchmarkHasSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	sel2 := DocW().Find(".tocnumber")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.HasSelection(sel2).Length()
+		} else {
+			sel.HasSelection(sel2)
+		}
+	}
+	if n != 15 {
+		b.Fatalf("want 15, got %d", n)
+	}
+}
+
+func BenchmarkEnd(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li").Has(".tocnumber")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.End().Length()
+		} else {
+			sel.End()
+		}
+	}
+	if n != 373 {
+		b.Fatalf("want 373, got %d", n)
+	}
+}

+ 68 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench_iteration_test.go

@@ -0,0 +1,68 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func BenchmarkEach(b *testing.B) {
+	var tmp, n int
+
+	b.StopTimer()
+	sel := DocW().Find("td")
+	f := func(i int, s *Selection) {
+		tmp++
+	}
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.Each(f)
+		if n == 0 {
+			n = tmp
+		}
+	}
+	if n != 59 {
+		b.Fatalf("want 59, got %d", n)
+	}
+}
+
+func BenchmarkMap(b *testing.B) {
+	var tmp, n int
+
+	b.StopTimer()
+	sel := DocW().Find("td")
+	f := func(i int, s *Selection) string {
+		tmp++
+		return string(tmp)
+	}
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.Map(f)
+		if n == 0 {
+			n = tmp
+		}
+	}
+	if n != 59 {
+		b.Fatalf("want 59, got %d", n)
+	}
+}
+
+func BenchmarkEachWithBreak(b *testing.B) {
+	var tmp, n int
+
+	b.StopTimer()
+	sel := DocW().Find("td")
+	f := func(i int, s *Selection) bool {
+		tmp++
+		return tmp < 10
+	}
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		tmp = 0
+		sel.EachWithBreak(f)
+		if n == 0 {
+			n = tmp
+		}
+	}
+	if n != 10 {
+		b.Fatalf("want 10, got %d", n)
+	}
+}

+ 51 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench_property_test.go

@@ -0,0 +1,51 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func BenchmarkAttr(b *testing.B) {
+	var s string
+
+	b.StopTimer()
+	sel := DocW().Find("h1")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		s, _ = sel.Attr("id")
+	}
+	if s != "firstHeading" {
+		b.Fatalf("want firstHeading, got %q", s)
+	}
+}
+
+func BenchmarkText(b *testing.B) {
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.Text()
+	}
+}
+
+func BenchmarkLength(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		n = sel.Length()
+	}
+	if n != 14 {
+		b.Fatalf("want 14, got %d", n)
+	}
+}
+
+func BenchmarkHtml(b *testing.B) {
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		sel.Html()
+	}
+}

+ 111 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench_query_test.go

@@ -0,0 +1,111 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func BenchmarkIs(b *testing.B) {
+	var y bool
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		y = sel.Is(".toclevel-2")
+	}
+	if !y {
+		b.Fatal("want true")
+	}
+}
+
+func BenchmarkIsPositional(b *testing.B) {
+	var y bool
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		y = sel.Is("li:nth-child(2)")
+	}
+	if !y {
+		b.Fatal("want true")
+	}
+}
+
+func BenchmarkIsFunction(b *testing.B) {
+	var y bool
+
+	b.StopTimer()
+	sel := DocW().Find(".toclevel-1")
+	f := func(i int, s *Selection) bool {
+		return i == 8
+	}
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		y = sel.IsFunction(f)
+	}
+	if !y {
+		b.Fatal("want true")
+	}
+}
+
+func BenchmarkIsSelection(b *testing.B) {
+	var y bool
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	sel2 := DocW().Find(".toclevel-2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		y = sel.IsSelection(sel2)
+	}
+	if !y {
+		b.Fatal("want true")
+	}
+}
+
+func BenchmarkIsNodes(b *testing.B) {
+	var y bool
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	sel2 := DocW().Find(".toclevel-2")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		y = sel.IsNodes(nodes...)
+	}
+	if !y {
+		b.Fatal("want true")
+	}
+}
+
+func BenchmarkHasClass(b *testing.B) {
+	var y bool
+
+	b.StopTimer()
+	sel := DocW().Find("span")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		y = sel.HasClass("official")
+	}
+	if !y {
+		b.Fatal("want true")
+	}
+}
+
+func BenchmarkContains(b *testing.B) {
+	var y bool
+
+	b.StopTimer()
+	sel := DocW().Find("span.url")
+	sel2 := DocW().Find("a[rel=\"nofollow\"]")
+	node := sel2.Nodes[0]
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		y = sel.Contains(node)
+	}
+	if !y {
+		b.Fatal("want true")
+	}
+}

+ 802 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/bench_traversal_test.go

@@ -0,0 +1,802 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func BenchmarkFind(b *testing.B) {
+	var n int
+
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = DocB().Find("dd").Length()
+
+		} else {
+			DocB().Find("dd")
+		}
+	}
+	if n != 41 {
+		b.Fatalf("want 41, got %d", n)
+	}
+}
+
+func BenchmarkFindWithinSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("ul")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Find("a[class]").Length()
+		} else {
+			sel.Find("a[class]")
+		}
+	}
+	if n != 39 {
+		b.Fatalf("want 39, got %d", n)
+	}
+}
+
+func BenchmarkFindSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("ul")
+	sel2 := DocW().Find("span")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.FindSelection(sel2).Length()
+		} else {
+			sel.FindSelection(sel2)
+		}
+	}
+	if n != 73 {
+		b.Fatalf("want 73, got %d", n)
+	}
+}
+
+func BenchmarkFindNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("ul")
+	sel2 := DocW().Find("span")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.FindNodes(nodes...).Length()
+		} else {
+			sel.FindNodes(nodes...)
+		}
+	}
+	if n != 73 {
+		b.Fatalf("want 73, got %d", n)
+	}
+}
+
+func BenchmarkContents(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find(".toclevel-1")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Contents().Length()
+		} else {
+			sel.Contents()
+		}
+	}
+	if n != 16 {
+		b.Fatalf("want 16, got %d", n)
+	}
+}
+
+func BenchmarkContentsFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find(".toclevel-1")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ContentsFiltered("a[href=\"#Examples\"]").Length()
+		} else {
+			sel.ContentsFiltered("a[href=\"#Examples\"]")
+		}
+	}
+	if n != 1 {
+		b.Fatalf("want 1, got %d", n)
+	}
+}
+
+func BenchmarkChildren(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find(".toclevel-2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Children().Length()
+		} else {
+			sel.Children()
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkChildrenFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h3")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ChildrenFiltered(".editsection").Length()
+		} else {
+			sel.ChildrenFiltered(".editsection")
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkParent(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Parent().Length()
+		} else {
+			sel.Parent()
+		}
+	}
+	if n != 55 {
+		b.Fatalf("want 55, got %d", n)
+	}
+}
+
+func BenchmarkParentFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ParentFiltered("ul[id]").Length()
+		} else {
+			sel.ParentFiltered("ul[id]")
+		}
+	}
+	if n != 4 {
+		b.Fatalf("want 4, got %d", n)
+	}
+}
+
+func BenchmarkParents(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("th a")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Parents().Length()
+		} else {
+			sel.Parents()
+		}
+	}
+	if n != 73 {
+		b.Fatalf("want 73, got %d", n)
+	}
+}
+
+func BenchmarkParentsFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("th a")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ParentsFiltered("tr").Length()
+		} else {
+			sel.ParentsFiltered("tr")
+		}
+	}
+	if n != 18 {
+		b.Fatalf("want 18, got %d", n)
+	}
+}
+
+func BenchmarkParentsUntil(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("th a")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ParentsUntil("table").Length()
+		} else {
+			sel.ParentsUntil("table")
+		}
+	}
+	if n != 52 {
+		b.Fatalf("want 52, got %d", n)
+	}
+}
+
+func BenchmarkParentsUntilSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("th a")
+	sel2 := DocW().Find("#content")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ParentsUntilSelection(sel2).Length()
+		} else {
+			sel.ParentsUntilSelection(sel2)
+		}
+	}
+	if n != 70 {
+		b.Fatalf("want 70, got %d", n)
+	}
+}
+
+func BenchmarkParentsUntilNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("th a")
+	sel2 := DocW().Find("#content")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ParentsUntilNodes(nodes...).Length()
+		} else {
+			sel.ParentsUntilNodes(nodes...)
+		}
+	}
+	if n != 70 {
+		b.Fatalf("want 70, got %d", n)
+	}
+}
+
+func BenchmarkParentsFilteredUntil(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find(".toclevel-1 a")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ParentsFilteredUntil(":nth-child(1)", "ul").Length()
+		} else {
+			sel.ParentsFilteredUntil(":nth-child(1)", "ul")
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkParentsFilteredUntilSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find(".toclevel-1 a")
+	sel2 := DocW().Find("ul")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ParentsFilteredUntilSelection(":nth-child(1)", sel2).Length()
+		} else {
+			sel.ParentsFilteredUntilSelection(":nth-child(1)", sel2)
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkParentsFilteredUntilNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find(".toclevel-1 a")
+	sel2 := DocW().Find("ul")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ParentsFilteredUntilNodes(":nth-child(1)", nodes...).Length()
+		} else {
+			sel.ParentsFilteredUntilNodes(":nth-child(1)", nodes...)
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkSiblings(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("ul li:nth-child(1)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Siblings().Length()
+		} else {
+			sel.Siblings()
+		}
+	}
+	if n != 293 {
+		b.Fatalf("want 293, got %d", n)
+	}
+}
+
+func BenchmarkSiblingsFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("ul li:nth-child(1)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.SiblingsFiltered("[class]").Length()
+		} else {
+			sel.SiblingsFiltered("[class]")
+		}
+	}
+	if n != 46 {
+		b.Fatalf("want 46, got %d", n)
+	}
+}
+
+func BenchmarkNext(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:nth-child(1)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Next().Length()
+		} else {
+			sel.Next()
+		}
+	}
+	if n != 49 {
+		b.Fatalf("want 49, got %d", n)
+	}
+}
+
+func BenchmarkNextFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:nth-child(1)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextFiltered("[class]").Length()
+		} else {
+			sel.NextFiltered("[class]")
+		}
+	}
+	if n != 6 {
+		b.Fatalf("want 6, got %d", n)
+	}
+}
+
+func BenchmarkNextAll(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:nth-child(3)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextAll().Length()
+		} else {
+			sel.NextAll()
+		}
+	}
+	if n != 234 {
+		b.Fatalf("want 234, got %d", n)
+	}
+}
+
+func BenchmarkNextAllFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:nth-child(3)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextAllFiltered("[class]").Length()
+		} else {
+			sel.NextAllFiltered("[class]")
+		}
+	}
+	if n != 33 {
+		b.Fatalf("want 33, got %d", n)
+	}
+}
+
+func BenchmarkPrev(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:last-child")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Prev().Length()
+		} else {
+			sel.Prev()
+		}
+	}
+	if n != 49 {
+		b.Fatalf("want 49, got %d", n)
+	}
+}
+
+func BenchmarkPrevFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:last-child")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevFiltered("[class]").Length()
+		} else {
+			sel.PrevFiltered("[class]")
+		}
+	}
+	// There is one more Prev li with a class, compared to Next li with a class
+	// (confirmed by looking at the HTML, this is ok)
+	if n != 7 {
+		b.Fatalf("want 7, got %d", n)
+	}
+}
+
+func BenchmarkPrevAll(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:nth-child(4)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevAll().Length()
+		} else {
+			sel.PrevAll()
+		}
+	}
+	if n != 78 {
+		b.Fatalf("want 78, got %d", n)
+	}
+}
+
+func BenchmarkPrevAllFiltered(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:nth-child(4)")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevAllFiltered("[class]").Length()
+		} else {
+			sel.PrevAllFiltered("[class]")
+		}
+	}
+	if n != 6 {
+		b.Fatalf("want 6, got %d", n)
+	}
+}
+
+func BenchmarkNextUntil(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:first-child")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextUntil(":nth-child(4)").Length()
+		} else {
+			sel.NextUntil(":nth-child(4)")
+		}
+	}
+	if n != 84 {
+		b.Fatalf("want 84, got %d", n)
+	}
+}
+
+func BenchmarkNextUntilSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	sel2 := DocW().Find("ul")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextUntilSelection(sel2).Length()
+		} else {
+			sel.NextUntilSelection(sel2)
+		}
+	}
+	if n != 42 {
+		b.Fatalf("want 42, got %d", n)
+	}
+}
+
+func BenchmarkNextUntilNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	sel2 := DocW().Find("p")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextUntilNodes(nodes...).Length()
+		} else {
+			sel.NextUntilNodes(nodes...)
+		}
+	}
+	if n != 12 {
+		b.Fatalf("want 12, got %d", n)
+	}
+}
+
+func BenchmarkPrevUntil(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("li:last-child")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevUntil(":nth-child(4)").Length()
+		} else {
+			sel.PrevUntil(":nth-child(4)")
+		}
+	}
+	if n != 238 {
+		b.Fatalf("want 238, got %d", n)
+	}
+}
+
+func BenchmarkPrevUntilSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	sel2 := DocW().Find("ul")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevUntilSelection(sel2).Length()
+		} else {
+			sel.PrevUntilSelection(sel2)
+		}
+	}
+	if n != 49 {
+		b.Fatalf("want 49, got %d", n)
+	}
+}
+
+func BenchmarkPrevUntilNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	sel2 := DocW().Find("p")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevUntilNodes(nodes...).Length()
+		} else {
+			sel.PrevUntilNodes(nodes...)
+		}
+	}
+	if n != 11 {
+		b.Fatalf("want 11, got %d", n)
+	}
+}
+
+func BenchmarkNextFilteredUntil(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextFilteredUntil("p", "div").Length()
+		} else {
+			sel.NextFilteredUntil("p", "div")
+		}
+	}
+	if n != 22 {
+		b.Fatalf("want 22, got %d", n)
+	}
+}
+
+func BenchmarkNextFilteredUntilSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	sel2 := DocW().Find("div")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextFilteredUntilSelection("p", sel2).Length()
+		} else {
+			sel.NextFilteredUntilSelection("p", sel2)
+		}
+	}
+	if n != 22 {
+		b.Fatalf("want 22, got %d", n)
+	}
+}
+
+func BenchmarkNextFilteredUntilNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	sel2 := DocW().Find("div")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.NextFilteredUntilNodes("p", nodes...).Length()
+		} else {
+			sel.NextFilteredUntilNodes("p", nodes...)
+		}
+	}
+	if n != 22 {
+		b.Fatalf("want 22, got %d", n)
+	}
+}
+
+func BenchmarkPrevFilteredUntil(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevFilteredUntil("p", "div").Length()
+		} else {
+			sel.PrevFilteredUntil("p", "div")
+		}
+	}
+	if n != 20 {
+		b.Fatalf("want 20, got %d", n)
+	}
+}
+
+func BenchmarkPrevFilteredUntilSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	sel2 := DocW().Find("div")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevFilteredUntilSelection("p", sel2).Length()
+		} else {
+			sel.PrevFilteredUntilSelection("p", sel2)
+		}
+	}
+	if n != 20 {
+		b.Fatalf("want 20, got %d", n)
+	}
+}
+
+func BenchmarkPrevFilteredUntilNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := DocW().Find("h2")
+	sel2 := DocW().Find("div")
+	nodes := sel2.Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.PrevFilteredUntilNodes("p", nodes...).Length()
+		} else {
+			sel.PrevFilteredUntilNodes("p", nodes...)
+		}
+	}
+	if n != 20 {
+		b.Fatalf("want 20, got %d", n)
+	}
+}
+
+func BenchmarkClosest(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := Doc().Find(".container-fluid")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.Closest(".pvk-content").Length()
+		} else {
+			sel.Closest(".pvk-content")
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkClosestSelection(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".pvk-content")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ClosestSelection(sel2).Length()
+		} else {
+			sel.ClosestSelection(sel2)
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}
+
+func BenchmarkClosestNodes(b *testing.B) {
+	var n int
+
+	b.StopTimer()
+	sel := Doc().Find(".container-fluid")
+	nodes := Doc().Find(".pvk-content").Nodes
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		if n == 0 {
+			n = sel.ClosestNodes(nodes...).Length()
+		} else {
+			sel.ClosestNodes(nodes...)
+		}
+	}
+	if n != 2 {
+		b.Fatalf("want 2, got %d", n)
+	}
+}

+ 123 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/doc.go

@@ -0,0 +1,123 @@
+// Copyright (c) 2012-2016, Martin Angers & Contributors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// * Neither the name of the author nor the names of its contributors may be used to
+// endorse or promote products derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+// WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/*
+Package goquery implements features similar to jQuery, including the chainable
+syntax, to manipulate and query an HTML document.
+
+It brings a syntax and a set of features similar to jQuery to the Go language.
+It is based on Go's net/html package and the CSS Selector library cascadia.
+Since the net/html parser returns nodes, and not a full-featured DOM
+tree, jQuery's stateful manipulation functions (like height(), css(), detach())
+have been left off.
+
+Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is
+the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML.
+See the repository's wiki for various options on how to do this.
+
+Syntax-wise, it is as close as possible to jQuery, with the same method names when
+possible, and that warm and fuzzy chainable interface. jQuery being the
+ultra-popular library that it is, writing a similar HTML-manipulating
+library was better to follow its API than to start anew (in the same spirit as
+Go's fmt package), even though some of its methods are less than intuitive (looking
+at you, index()...).
+
+It is hosted on GitHub, along with additional documentation in the README.md
+file: https://github.com/puerkitobio/goquery
+
+Please note that because of the net/html dependency, goquery requires Go1.1+.
+
+The various methods are split into files based on the category of behavior.
+The three dots (...) indicate that various "overloads" are available.
+
+* array.go : array-like positional manipulation of the selection.
+    - Eq()
+    - First()
+    - Get()
+    - Index...()
+    - Last()
+    - Slice()
+
+* expand.go : methods that expand or augment the selection's set.
+    - Add...()
+    - AndSelf()
+    - Union(), which is an alias for AddSelection()
+
+* filter.go : filtering methods, that reduce the selection's set.
+    - End()
+    - Filter...()
+    - Has...()
+    - Intersection(), which is an alias of FilterSelection()
+    - Not...()
+
+* iteration.go : methods to loop over the selection's nodes.
+    - Each()
+    - EachWithBreak()
+    - Map()
+
+* manipulation.go : methods for modifying the document
+    - After...()
+    - Append...()
+    - Before...()
+    - Clone()
+    - Empty()
+    - Prepend...()
+    - Remove...()
+    - ReplaceWith...()
+    - Unwrap()
+    - Wrap...()
+    - WrapAll...()
+    - WrapInner...()
+
+* property.go : methods that inspect and get the node's properties values.
+    - Attr*(), RemoveAttr(), SetAttr()
+    - AddClass(), HasClass(), RemoveClass(), ToggleClass()
+    - Html()
+    - Length()
+    - Size(), which is an alias for Length()
+    - Text()
+
+* query.go : methods that query, or reflect, a node's identity.
+    - Contains()
+    - Is...()
+
+* traversal.go : methods to traverse the HTML document tree.
+    - Children...()
+    - Contents()
+    - Find...()
+    - Next...()
+    - Parent[s]...()
+    - Prev...()
+    - Siblings...()
+
+* type.go : definition of the types exposed by goquery.
+    - Document
+    - Selection
+    - Matcher
+
+* utilities.go : definition of helper functions (and not methods on a *Selection)
+that are not part of jQuery, but are useful to goquery.
+    - NodeName
+    - OuterHtml
+*/
+package goquery

+ 68 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/doc/tips.md

@@ -0,0 +1,68 @@
+# Tips and tricks
+
+## Handle Non-UTF8 html Pages
+
+The `go.net/html` package used by `goquery` requires that the html document is UTF-8 encoded. When you know the encoding of the html page is not UTF-8, you can use the `iconv` package to convert it to UTF-8 (there are various implementation of the `iconv` API, see [godoc.org][iconv] for other options):
+
+```
+$ go get -u github.com/djimenez/iconv-go
+```
+
+and then:
+
+```
+// Load the URL
+res, err := http.Get(url)
+if err != nil {
+    // handle error
+}
+defer res.Body.Close()
+
+// Convert the designated charset HTML to utf-8 encoded HTML.
+// `charset` being one of the charsets known by the iconv package.
+utfBody, err := iconv.NewReader(res.Body, charset, "utf-8")
+if err != nil {
+    // handler error
+}
+
+// use utfBody using goquery
+doc, err := goquery.NewDocumentFromReader(utfBody)
+if err != nil {
+    // handler error
+}
+// use doc...
+```
+
+Thanks to github user @YuheiNakasaka.
+
+Actually, the official go.text repository covers this use case too, see its [godoc page][text] for the details.
+
+
+## Handle Javascript-based Pages
+
+`goquery` is great to handle normal html pages, but when most of the page is build dynamically using javascript, there's not much it can do. There are various options when faced with this problem:
+
+* Use a headless browser such as [webloop][].
+* Use a Go javascript parser package, such as [otto][].
+
+You can find a code example using `otto` [in this gist][exotto]. Thanks to github user @cryptix.
+
+## For Loop
+
+If all you need is a normal `for` loop over all nodes in the current selection, where `Map/Each`-style iteration is not necessary, you can use the following:
+
+```
+sel := Doc().Find(".selector")
+for i := range sel.Nodes {
+	single := sel.Eq(i)
+    // use `single` as a selection of 1 node
+}
+```
+
+Thanks to github user @jmoiron.
+
+[webloop]: https://github.com/sourcegraph/webloop
+[otto]: https://github.com/robertkrimen/otto
+[exotto]: https://gist.github.com/cryptix/87127f76a94183747b53
+[iconv]: http://godoc.org/?q=iconv
+[text]: https://godoc.org/golang.org/x/text/encoding

+ 82 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/example_test.go

@@ -0,0 +1,82 @@
+package goquery_test
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/PuerkitoBio/goquery"
+)
+
+// This example scrapes the reviews shown on the home page of metalsucks.net.
+func Example() {
+	// Request the HTML page.
+	res, err := http.Get("http://metalsucks.net")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer res.Body.Close()
+	if res.StatusCode != 200 {
+		log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
+	}
+
+	// Load the HTML document
+	doc, err := goquery.NewDocumentFromReader(res.Body)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Find the review items
+	doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) {
+		// For each item found, get the band and title
+		band := s.Find("a").Text()
+		title := s.Find("i").Text()
+		fmt.Printf("Review %d: %s - %s\n", i, band, title)
+	})
+	// To see the output of the Example while running the test suite (go test), simply
+	// remove the leading "x" before Output on the next line. This will cause the
+	// example to fail (all the "real" tests should pass).
+
+	// xOutput: voluntarily fail the Example output.
+}
+
+// This example shows how to use NewDocumentFromReader from a file.
+func ExampleNewDocumentFromReader_file() {
+	// create from a file
+	f, err := os.Open("some/file.html")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer f.Close()
+	doc, err := goquery.NewDocumentFromReader(f)
+	if err != nil {
+		log.Fatal(err)
+	}
+	// use the goquery document...
+	_ = doc.Find("h1")
+}
+
+// This example shows how to use NewDocumentFromReader from a string.
+func ExampleNewDocumentFromReader_string() {
+	// create from a string
+	data := `
+<html>
+	<head>
+		<title>My document</title>
+	</head>
+	<body>
+		<h1>Header</h1>
+	</body>
+</html>`
+
+	doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
+	if err != nil {
+		log.Fatal(err)
+	}
+	header := doc.Find("h1").Text()
+	fmt.Println(header)
+
+	// Output: Header
+}

+ 70 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/expand.go

@@ -0,0 +1,70 @@
+package goquery
+
+import "golang.org/x/net/html"
+
+// Add adds the selector string's matching nodes to those in the current
+// selection and returns a new Selection object.
+// The selector string is run in the context of the document of the current
+// Selection object.
+func (s *Selection) Add(selector string) *Selection {
+	return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, compileMatcher(selector))...)
+}
+
+// AddMatcher adds the matcher's matching nodes to those in the current
+// selection and returns a new Selection object.
+// The matcher is run in the context of the document of the current
+// Selection object.
+func (s *Selection) AddMatcher(m Matcher) *Selection {
+	return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, m)...)
+}
+
+// AddSelection adds the specified Selection object's nodes to those in the
+// current selection and returns a new Selection object.
+func (s *Selection) AddSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return s.AddNodes()
+	}
+	return s.AddNodes(sel.Nodes...)
+}
+
+// Union is an alias for AddSelection.
+func (s *Selection) Union(sel *Selection) *Selection {
+	return s.AddSelection(sel)
+}
+
+// AddNodes adds the specified nodes to those in the
+// current selection and returns a new Selection object.
+func (s *Selection) AddNodes(nodes ...*html.Node) *Selection {
+	return pushStack(s, appendWithoutDuplicates(s.Nodes, nodes, nil))
+}
+
+// AndSelf adds the previous set of elements on the stack to the current set.
+// It returns a new Selection object containing the current Selection combined
+// with the previous one.
+// Deprecated: This function has been deprecated and is now an alias for AddBack().
+func (s *Selection) AndSelf() *Selection {
+	return s.AddBack()
+}
+
+// AddBack adds the previous set of elements on the stack to the current set.
+// It returns a new Selection object containing the current Selection combined
+// with the previous one.
+func (s *Selection) AddBack() *Selection {
+	return s.AddSelection(s.prevSel)
+}
+
+// AddBackFiltered reduces the previous set of elements on the stack to those that
+// match the selector string, and adds them to the current set.
+// It returns a new Selection object containing the current Selection combined
+// with the filtered previous one
+func (s *Selection) AddBackFiltered(selector string) *Selection {
+	return s.AddSelection(s.prevSel.Filter(selector))
+}
+
+// AddBackMatcher reduces the previous set of elements on the stack to those that match
+// the mateher, and adds them to the curernt set.
+// It returns a new Selection object containing the current Selection combined
+// with the filtered previous one
+func (s *Selection) AddBackMatcher(m Matcher) *Selection {
+	return s.AddSelection(s.prevSel.FilterMatcher(m))
+}

+ 118 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/expand_test.go

@@ -0,0 +1,118 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func TestAdd(t *testing.T) {
+	sel := Doc().Find("div.row-fluid").Add("a")
+	assertLength(t, sel.Nodes, 19)
+}
+
+func TestAddInvalid(t *testing.T) {
+	sel1 := Doc().Find("div.row-fluid")
+	sel2 := sel1.Add("")
+	assertLength(t, sel1.Nodes, 9)
+	assertLength(t, sel2.Nodes, 9)
+	if sel1 == sel2 {
+		t.Errorf("selections should not be the same")
+	}
+}
+
+func TestAddRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Add("a").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestAddSelection(t *testing.T) {
+	sel := Doc().Find("div.row-fluid")
+	sel2 := Doc().Find("a")
+	sel = sel.AddSelection(sel2)
+	assertLength(t, sel.Nodes, 19)
+}
+
+func TestAddSelectionNil(t *testing.T) {
+	sel := Doc().Find("div.row-fluid")
+	assertLength(t, sel.Nodes, 9)
+
+	sel = sel.AddSelection(nil)
+	assertLength(t, sel.Nodes, 9)
+}
+
+func TestAddSelectionRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Find("a")
+	sel2 = sel.AddSelection(sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestAddNodes(t *testing.T) {
+	sel := Doc().Find("div.pvk-gutter")
+	sel2 := Doc().Find(".pvk-content")
+	sel = sel.AddNodes(sel2.Nodes...)
+	assertLength(t, sel.Nodes, 9)
+}
+
+func TestAddNodesNone(t *testing.T) {
+	sel := Doc().Find("div.pvk-gutter").AddNodes()
+	assertLength(t, sel.Nodes, 6)
+}
+
+func TestAddNodesRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Find("a")
+	sel2 = sel.AddNodes(sel2.Nodes...).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestAddNodesBig(t *testing.T) {
+	doc := DocW()
+	sel := doc.Find("li")
+	assertLength(t, sel.Nodes, 373)
+	sel2 := doc.Find("xyz")
+	assertLength(t, sel2.Nodes, 0)
+
+	nodes := sel.Nodes
+	sel2 = sel2.AddNodes(nodes...)
+	assertLength(t, sel2.Nodes, 373)
+	nodes2 := append(nodes, nodes...)
+	sel2 = sel2.End().AddNodes(nodes2...)
+	assertLength(t, sel2.Nodes, 373)
+	nodes3 := append(nodes2, nodes...)
+	sel2 = sel2.End().AddNodes(nodes3...)
+	assertLength(t, sel2.Nodes, 373)
+}
+
+func TestAndSelf(t *testing.T) {
+	sel := Doc().Find(".span12").Last().AndSelf()
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestAndSelfRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Find("a").AndSelf().End().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestAddBack(t *testing.T) {
+	sel := Doc().Find(".span12").Last().AddBack()
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestAddBackRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Find("a").AddBack().End().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestAddBackFiltered(t *testing.T) {
+	sel := Doc().Find(".span12, .footer").Find("h1").AddBackFiltered(".footer")
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestAddBackFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".span12, .footer")
+	sel2 := sel.Find("h1").AddBackFiltered(".footer").End().End()
+	assertEqual(t, sel, sel2)
+}

+ 163 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/filter.go

@@ -0,0 +1,163 @@
+package goquery
+
+import "golang.org/x/net/html"
+
+// Filter reduces the set of matched elements to those that match the selector string.
+// It returns a new Selection object for this subset of matching elements.
+func (s *Selection) Filter(selector string) *Selection {
+	return s.FilterMatcher(compileMatcher(selector))
+}
+
+// FilterMatcher reduces the set of matched elements to those that match
+// the given matcher. It returns a new Selection object for this subset
+// of matching elements.
+func (s *Selection) FilterMatcher(m Matcher) *Selection {
+	return pushStack(s, winnow(s, m, true))
+}
+
+// Not removes elements from the Selection that match the selector string.
+// It returns a new Selection object with the matching elements removed.
+func (s *Selection) Not(selector string) *Selection {
+	return s.NotMatcher(compileMatcher(selector))
+}
+
+// NotMatcher removes elements from the Selection that match the given matcher.
+// It returns a new Selection object with the matching elements removed.
+func (s *Selection) NotMatcher(m Matcher) *Selection {
+	return pushStack(s, winnow(s, m, false))
+}
+
+// FilterFunction reduces the set of matched elements to those that pass the function's test.
+// It returns a new Selection object for this subset of elements.
+func (s *Selection) FilterFunction(f func(int, *Selection) bool) *Selection {
+	return pushStack(s, winnowFunction(s, f, true))
+}
+
+// NotFunction removes elements from the Selection that pass the function's test.
+// It returns a new Selection object with the matching elements removed.
+func (s *Selection) NotFunction(f func(int, *Selection) bool) *Selection {
+	return pushStack(s, winnowFunction(s, f, false))
+}
+
+// FilterNodes reduces the set of matched elements to those that match the specified nodes.
+// It returns a new Selection object for this subset of elements.
+func (s *Selection) FilterNodes(nodes ...*html.Node) *Selection {
+	return pushStack(s, winnowNodes(s, nodes, true))
+}
+
+// NotNodes removes elements from the Selection that match the specified nodes.
+// It returns a new Selection object with the matching elements removed.
+func (s *Selection) NotNodes(nodes ...*html.Node) *Selection {
+	return pushStack(s, winnowNodes(s, nodes, false))
+}
+
+// FilterSelection reduces the set of matched elements to those that match a
+// node in the specified Selection object.
+// It returns a new Selection object for this subset of elements.
+func (s *Selection) FilterSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return pushStack(s, winnowNodes(s, nil, true))
+	}
+	return pushStack(s, winnowNodes(s, sel.Nodes, true))
+}
+
+// NotSelection removes elements from the Selection that match a node in the specified
+// Selection object. It returns a new Selection object with the matching elements removed.
+func (s *Selection) NotSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return pushStack(s, winnowNodes(s, nil, false))
+	}
+	return pushStack(s, winnowNodes(s, sel.Nodes, false))
+}
+
+// Intersection is an alias for FilterSelection.
+func (s *Selection) Intersection(sel *Selection) *Selection {
+	return s.FilterSelection(sel)
+}
+
+// Has reduces the set of matched elements to those that have a descendant
+// that matches the selector.
+// It returns a new Selection object with the matching elements.
+func (s *Selection) Has(selector string) *Selection {
+	return s.HasSelection(s.document.Find(selector))
+}
+
+// HasMatcher reduces the set of matched elements to those that have a descendant
+// that matches the matcher.
+// It returns a new Selection object with the matching elements.
+func (s *Selection) HasMatcher(m Matcher) *Selection {
+	return s.HasSelection(s.document.FindMatcher(m))
+}
+
+// HasNodes reduces the set of matched elements to those that have a
+// descendant that matches one of the nodes.
+// It returns a new Selection object with the matching elements.
+func (s *Selection) HasNodes(nodes ...*html.Node) *Selection {
+	return s.FilterFunction(func(_ int, sel *Selection) bool {
+		// Add all nodes that contain one of the specified nodes
+		for _, n := range nodes {
+			if sel.Contains(n) {
+				return true
+			}
+		}
+		return false
+	})
+}
+
+// HasSelection reduces the set of matched elements to those that have a
+// descendant that matches one of the nodes of the specified Selection object.
+// It returns a new Selection object with the matching elements.
+func (s *Selection) HasSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return s.HasNodes()
+	}
+	return s.HasNodes(sel.Nodes...)
+}
+
+// End ends the most recent filtering operation in the current chain and
+// returns the set of matched elements to its previous state.
+func (s *Selection) End() *Selection {
+	if s.prevSel != nil {
+		return s.prevSel
+	}
+	return newEmptySelection(s.document)
+}
+
+// Filter based on the matcher, and the indicator to keep (Filter) or
+// to get rid of (Not) the matching elements.
+func winnow(sel *Selection, m Matcher, keep bool) []*html.Node {
+	// Optimize if keep is requested
+	if keep {
+		return m.Filter(sel.Nodes)
+	}
+	// Use grep
+	return grep(sel, func(i int, s *Selection) bool {
+		return !m.Match(s.Get(0))
+	})
+}
+
+// Filter based on an array of nodes, and the indicator to keep (Filter) or
+// to get rid of (Not) the matching elements.
+func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node {
+	if len(nodes)+len(sel.Nodes) < minNodesForSet {
+		return grep(sel, func(i int, s *Selection) bool {
+			return isInSlice(nodes, s.Get(0)) == keep
+		})
+	}
+
+	set := make(map[*html.Node]bool)
+	for _, n := range nodes {
+		set[n] = true
+	}
+	return grep(sel, func(i int, s *Selection) bool {
+		return set[s.Get(0)] == keep
+	})
+}
+
+// Filter based on a function test, and the indicator to keep (Filter) or
+// to get rid of (Not) the matching elements.
+func winnowFunction(sel *Selection, f func(int, *Selection) bool, keep bool) []*html.Node {
+	return grep(sel, func(i int, s *Selection) bool {
+		return f(i, s) == keep
+	})
+}

+ 206 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/filter_test.go

@@ -0,0 +1,206 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func TestFilter(t *testing.T) {
+	sel := Doc().Find(".span12").Filter(".alert")
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestFilterNone(t *testing.T) {
+	sel := Doc().Find(".span12").Filter(".zzalert")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestFilterInvalid(t *testing.T) {
+	sel := Doc().Find(".span12").Filter("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestFilterRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Filter(".alert").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestFilterFunction(t *testing.T) {
+	sel := Doc().Find(".pvk-content").FilterFunction(func(i int, s *Selection) bool {
+		return i > 0
+	})
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestFilterFunctionRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.FilterFunction(func(i int, s *Selection) bool {
+		return i > 0
+	}).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestFilterNode(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.FilterNodes(sel.Nodes[2])
+	assertLength(t, sel2.Nodes, 1)
+}
+
+func TestFilterNodeRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.FilterNodes(sel.Nodes[2]).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestFilterSelection(t *testing.T) {
+	sel := Doc().Find(".link")
+	sel2 := Doc().Find("a[ng-click]")
+	sel3 := sel.FilterSelection(sel2)
+	assertLength(t, sel3.Nodes, 1)
+}
+
+func TestFilterSelectionRollback(t *testing.T) {
+	sel := Doc().Find(".link")
+	sel2 := Doc().Find("a[ng-click]")
+	sel2 = sel.FilterSelection(sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestFilterSelectionNil(t *testing.T) {
+	var sel2 *Selection
+
+	sel := Doc().Find(".link")
+	sel3 := sel.FilterSelection(sel2)
+	assertLength(t, sel3.Nodes, 0)
+}
+
+func TestNot(t *testing.T) {
+	sel := Doc().Find(".span12").Not(".alert")
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestNotInvalid(t *testing.T) {
+	sel := Doc().Find(".span12").Not("")
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestNotRollback(t *testing.T) {
+	sel := Doc().Find(".span12")
+	sel2 := sel.Not(".alert").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNotNone(t *testing.T) {
+	sel := Doc().Find(".span12").Not(".zzalert")
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestNotFunction(t *testing.T) {
+	sel := Doc().Find(".pvk-content").NotFunction(func(i int, s *Selection) bool {
+		return i > 0
+	})
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestNotFunctionRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.NotFunction(func(i int, s *Selection) bool {
+		return i > 0
+	}).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNotNode(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.NotNodes(sel.Nodes[2])
+	assertLength(t, sel2.Nodes, 2)
+}
+
+func TestNotNodeRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.NotNodes(sel.Nodes[2]).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNotSelection(t *testing.T) {
+	sel := Doc().Find(".link")
+	sel2 := Doc().Find("a[ng-click]")
+	sel3 := sel.NotSelection(sel2)
+	assertLength(t, sel3.Nodes, 6)
+}
+
+func TestNotSelectionRollback(t *testing.T) {
+	sel := Doc().Find(".link")
+	sel2 := Doc().Find("a[ng-click]")
+	sel2 = sel.NotSelection(sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestIntersection(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter")
+	sel2 := Doc().Find("div").Intersection(sel)
+	assertLength(t, sel2.Nodes, 6)
+}
+
+func TestIntersectionRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter")
+	sel2 := Doc().Find("div")
+	sel2 = sel.Intersection(sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestHas(t *testing.T) {
+	sel := Doc().Find(".container-fluid").Has(".center-content")
+	assertLength(t, sel.Nodes, 2)
+	// Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content
+}
+
+func TestHasInvalid(t *testing.T) {
+	sel := Doc().Find(".container-fluid").Has("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestHasRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.Has(".center-content").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestHasNodes(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".center-content")
+	sel = sel.HasNodes(sel2.Nodes...)
+	assertLength(t, sel.Nodes, 2)
+	// Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content
+}
+
+func TestHasNodesRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".center-content")
+	sel2 = sel.HasNodes(sel2.Nodes...).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestHasSelection(t *testing.T) {
+	sel := Doc().Find("p")
+	sel2 := Doc().Find("small")
+	sel = sel.HasSelection(sel2)
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestHasSelectionRollback(t *testing.T) {
+	sel := Doc().Find("p")
+	sel2 := Doc().Find("small")
+	sel2 = sel.HasSelection(sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestEnd(t *testing.T) {
+	sel := Doc().Find("p").Has("small").End()
+	assertLength(t, sel.Nodes, 4)
+}
+
+func TestEndToTop(t *testing.T) {
+	sel := Doc().Find("p").Has("small").End().End().End()
+	assertLength(t, sel.Nodes, 0)
+}

+ 6 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/go.mod

@@ -0,0 +1,6 @@
+module github.com/PuerkitoBio/goquery
+
+require (
+	github.com/andybalholm/cascadia v1.0.0
+	golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
+)

+ 5 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/go.sum

@@ -0,0 +1,5 @@
+github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
+github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

+ 39 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/iteration.go

@@ -0,0 +1,39 @@
+package goquery
+
+// Each iterates over a Selection object, executing a function for each
+// matched element. It returns the current Selection object. The function
+// f is called for each element in the selection with the index of the
+// element in that selection starting at 0, and a *Selection that contains
+// only that element.
+func (s *Selection) Each(f func(int, *Selection)) *Selection {
+	for i, n := range s.Nodes {
+		f(i, newSingleSelection(n, s.document))
+	}
+	return s
+}
+
+// EachWithBreak iterates over a Selection object, executing a function for each
+// matched element. It is identical to Each except that it is possible to break
+// out of the loop by returning false in the callback function. It returns the
+// current Selection object.
+func (s *Selection) EachWithBreak(f func(int, *Selection) bool) *Selection {
+	for i, n := range s.Nodes {
+		if !f(i, newSingleSelection(n, s.document)) {
+			return s
+		}
+	}
+	return s
+}
+
+// Map passes each element in the current matched set through a function,
+// producing a slice of string holding the returned values. The function
+// f is called for each element in the selection with the index of the
+// element in that selection starting at 0, and a *Selection that contains
+// only that element.
+func (s *Selection) Map(f func(int, *Selection) string) (result []string) {
+	for i, n := range s.Nodes {
+		result = append(result, f(i, newSingleSelection(n, s.document)))
+	}
+
+	return result
+}

+ 88 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/iteration_test.go

@@ -0,0 +1,88 @@
+package goquery
+
+import (
+	"testing"
+
+	"golang.org/x/net/html"
+)
+
+func TestEach(t *testing.T) {
+	var cnt int
+
+	sel := Doc().Find(".hero-unit .row-fluid").Each(func(i int, n *Selection) {
+		cnt++
+		t.Logf("At index %v, node %v", i, n.Nodes[0].Data)
+	}).Find("a")
+
+	if cnt != 4 {
+		t.Errorf("Expected Each() to call function 4 times, got %v times.", cnt)
+	}
+	assertLength(t, sel.Nodes, 6)
+}
+
+func TestEachWithBreak(t *testing.T) {
+	var cnt int
+
+	sel := Doc().Find(".hero-unit .row-fluid").EachWithBreak(func(i int, n *Selection) bool {
+		cnt++
+		t.Logf("At index %v, node %v", i, n.Nodes[0].Data)
+		return false
+	}).Find("a")
+
+	if cnt != 1 {
+		t.Errorf("Expected Each() to call function 1 time, got %v times.", cnt)
+	}
+	assertLength(t, sel.Nodes, 6)
+}
+
+func TestEachEmptySelection(t *testing.T) {
+	var cnt int
+
+	sel := Doc().Find("zzzz")
+	sel.Each(func(i int, n *Selection) {
+		cnt++
+	})
+	if cnt > 0 {
+		t.Error("Expected Each() to not be called on empty Selection.")
+	}
+	sel2 := sel.Find("div")
+	assertLength(t, sel2.Nodes, 0)
+}
+
+func TestMap(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	vals := sel.Map(func(i int, s *Selection) string {
+		n := s.Get(0)
+		if n.Type == html.ElementNode {
+			return n.Data
+		}
+		return ""
+	})
+	for _, v := range vals {
+		if v != "div" {
+			t.Error("Expected Map array result to be all 'div's.")
+		}
+	}
+	if len(vals) != 3 {
+		t.Errorf("Expected Map array result to have a length of 3, found %v.", len(vals))
+	}
+}
+
+func TestForRange(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	initLen := sel.Length()
+	for i := range sel.Nodes {
+		single := sel.Eq(i)
+		//h, err := single.Html()
+		//if err != nil {
+		//	t.Fatal(err)
+		//}
+		//fmt.Println(i, h)
+		if single.Length() != 1 {
+			t.Errorf("%d: expected length of 1, got %d", i, single.Length())
+		}
+	}
+	if sel.Length() != initLen {
+		t.Errorf("expected initial selection to still have length %d, got %d", initLen, sel.Length())
+	}
+}

+ 574 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/manipulation.go

@@ -0,0 +1,574 @@
+package goquery
+
+import (
+	"strings"
+
+	"golang.org/x/net/html"
+)
+
+// After applies the selector from the root document and inserts the matched elements
+// after the elements in the set of matched elements.
+//
+// If one of the matched elements in the selection is not currently in the
+// document, it's impossible to insert nodes after it, so it will be ignored.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) After(selector string) *Selection {
+	return s.AfterMatcher(compileMatcher(selector))
+}
+
+// AfterMatcher applies the matcher from the root document and inserts the matched elements
+// after the elements in the set of matched elements.
+//
+// If one of the matched elements in the selection is not currently in the
+// document, it's impossible to insert nodes after it, so it will be ignored.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) AfterMatcher(m Matcher) *Selection {
+	return s.AfterNodes(m.MatchAll(s.document.rootNode)...)
+}
+
+// AfterSelection inserts the elements in the selection after each element in the set of matched
+// elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) AfterSelection(sel *Selection) *Selection {
+	return s.AfterNodes(sel.Nodes...)
+}
+
+// AfterHtml parses the html and inserts it after the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) AfterHtml(html string) *Selection {
+	return s.AfterNodes(parseHtml(html)...)
+}
+
+// AfterNodes inserts the nodes after each element in the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) AfterNodes(ns ...*html.Node) *Selection {
+	return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) {
+		if sn.Parent != nil {
+			sn.Parent.InsertBefore(n, sn.NextSibling)
+		}
+	})
+}
+
+// Append appends the elements specified by the selector to the end of each element
+// in the set of matched elements, following those rules:
+//
+// 1) The selector is applied to the root document.
+//
+// 2) Elements that are part of the document will be moved to the new location.
+//
+// 3) If there are multiple locations to append to, cloned nodes will be
+// appended to all target locations except the last one, which will be moved
+// as noted in (2).
+func (s *Selection) Append(selector string) *Selection {
+	return s.AppendMatcher(compileMatcher(selector))
+}
+
+// AppendMatcher appends the elements specified by the matcher to the end of each element
+// in the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) AppendMatcher(m Matcher) *Selection {
+	return s.AppendNodes(m.MatchAll(s.document.rootNode)...)
+}
+
+// AppendSelection appends the elements in the selection to the end of each element
+// in the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) AppendSelection(sel *Selection) *Selection {
+	return s.AppendNodes(sel.Nodes...)
+}
+
+// AppendHtml parses the html and appends it to the set of matched elements.
+func (s *Selection) AppendHtml(html string) *Selection {
+	return s.AppendNodes(parseHtml(html)...)
+}
+
+// AppendNodes appends the specified nodes to each node in the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) AppendNodes(ns ...*html.Node) *Selection {
+	return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) {
+		sn.AppendChild(n)
+	})
+}
+
+// Before inserts the matched elements before each element in the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) Before(selector string) *Selection {
+	return s.BeforeMatcher(compileMatcher(selector))
+}
+
+// BeforeMatcher inserts the matched elements before each element in the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) BeforeMatcher(m Matcher) *Selection {
+	return s.BeforeNodes(m.MatchAll(s.document.rootNode)...)
+}
+
+// BeforeSelection inserts the elements in the selection before each element in the set of matched
+// elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) BeforeSelection(sel *Selection) *Selection {
+	return s.BeforeNodes(sel.Nodes...)
+}
+
+// BeforeHtml parses the html and inserts it before the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) BeforeHtml(html string) *Selection {
+	return s.BeforeNodes(parseHtml(html)...)
+}
+
+// BeforeNodes inserts the nodes before each element in the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) BeforeNodes(ns ...*html.Node) *Selection {
+	return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) {
+		if sn.Parent != nil {
+			sn.Parent.InsertBefore(n, sn)
+		}
+	})
+}
+
+// Clone creates a deep copy of the set of matched nodes. The new nodes will not be
+// attached to the document.
+func (s *Selection) Clone() *Selection {
+	ns := newEmptySelection(s.document)
+	ns.Nodes = cloneNodes(s.Nodes)
+	return ns
+}
+
+// Empty removes all children nodes from the set of matched elements.
+// It returns the children nodes in a new Selection.
+func (s *Selection) Empty() *Selection {
+	var nodes []*html.Node
+
+	for _, n := range s.Nodes {
+		for c := n.FirstChild; c != nil; c = n.FirstChild {
+			n.RemoveChild(c)
+			nodes = append(nodes, c)
+		}
+	}
+
+	return pushStack(s, nodes)
+}
+
+// Prepend prepends the elements specified by the selector to each element in
+// the set of matched elements, following the same rules as Append.
+func (s *Selection) Prepend(selector string) *Selection {
+	return s.PrependMatcher(compileMatcher(selector))
+}
+
+// PrependMatcher prepends the elements specified by the matcher to each
+// element in the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) PrependMatcher(m Matcher) *Selection {
+	return s.PrependNodes(m.MatchAll(s.document.rootNode)...)
+}
+
+// PrependSelection prepends the elements in the selection to each element in
+// the set of matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) PrependSelection(sel *Selection) *Selection {
+	return s.PrependNodes(sel.Nodes...)
+}
+
+// PrependHtml parses the html and prepends it to the set of matched elements.
+func (s *Selection) PrependHtml(html string) *Selection {
+	return s.PrependNodes(parseHtml(html)...)
+}
+
+// PrependNodes prepends the specified nodes to each node in the set of
+// matched elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) PrependNodes(ns ...*html.Node) *Selection {
+	return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) {
+		// sn.FirstChild may be nil, in which case this functions like
+		// sn.AppendChild()
+		sn.InsertBefore(n, sn.FirstChild)
+	})
+}
+
+// Remove removes the set of matched elements from the document.
+// It returns the same selection, now consisting of nodes not in the document.
+func (s *Selection) Remove() *Selection {
+	for _, n := range s.Nodes {
+		if n.Parent != nil {
+			n.Parent.RemoveChild(n)
+		}
+	}
+
+	return s
+}
+
+// RemoveFiltered removes the set of matched elements by selector.
+// It returns the Selection of removed nodes.
+func (s *Selection) RemoveFiltered(selector string) *Selection {
+	return s.RemoveMatcher(compileMatcher(selector))
+}
+
+// RemoveMatcher removes the set of matched elements.
+// It returns the Selection of removed nodes.
+func (s *Selection) RemoveMatcher(m Matcher) *Selection {
+	return s.FilterMatcher(m).Remove()
+}
+
+// ReplaceWith replaces each element in the set of matched elements with the
+// nodes matched by the given selector.
+// It returns the removed elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) ReplaceWith(selector string) *Selection {
+	return s.ReplaceWithMatcher(compileMatcher(selector))
+}
+
+// ReplaceWithMatcher replaces each element in the set of matched elements with
+// the nodes matched by the given Matcher.
+// It returns the removed elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) ReplaceWithMatcher(m Matcher) *Selection {
+	return s.ReplaceWithNodes(m.MatchAll(s.document.rootNode)...)
+}
+
+// ReplaceWithSelection replaces each element in the set of matched elements with
+// the nodes from the given Selection.
+// It returns the removed elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) ReplaceWithSelection(sel *Selection) *Selection {
+	return s.ReplaceWithNodes(sel.Nodes...)
+}
+
+// ReplaceWithHtml replaces each element in the set of matched elements with
+// the parsed HTML.
+// It returns the removed elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) ReplaceWithHtml(html string) *Selection {
+	return s.ReplaceWithNodes(parseHtml(html)...)
+}
+
+// ReplaceWithNodes replaces each element in the set of matched elements with
+// the given nodes.
+// It returns the removed elements.
+//
+// This follows the same rules as Selection.Append.
+func (s *Selection) ReplaceWithNodes(ns ...*html.Node) *Selection {
+	s.AfterNodes(ns...)
+	return s.Remove()
+}
+
+// SetHtml sets the html content of each element in the selection to
+// specified html string.
+func (s *Selection) SetHtml(html string) *Selection {
+	return setHtmlNodes(s, parseHtml(html)...)
+}
+
+// SetText sets the content of each element in the selection to specified content.
+// The provided text string is escaped.
+func (s *Selection) SetText(text string) *Selection {
+	return s.SetHtml(html.EscapeString(text))
+}
+
+// Unwrap removes the parents of the set of matched elements, leaving the matched
+// elements (and their siblings, if any) in their place.
+// It returns the original selection.
+func (s *Selection) Unwrap() *Selection {
+	s.Parent().Each(func(i int, ss *Selection) {
+		// For some reason, jquery allows unwrap to remove the <head> element, so
+		// allowing it here too. Same for <html>. Why it allows those elements to
+		// be unwrapped while not allowing body is a mystery to me.
+		if ss.Nodes[0].Data != "body" {
+			ss.ReplaceWithSelection(ss.Contents())
+		}
+	})
+
+	return s
+}
+
+// Wrap wraps each element in the set of matched elements inside the first
+// element matched by the given selector. The matched child is cloned before
+// being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) Wrap(selector string) *Selection {
+	return s.WrapMatcher(compileMatcher(selector))
+}
+
+// WrapMatcher wraps each element in the set of matched elements inside the
+// first element matched by the given matcher. The matched child is cloned
+// before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapMatcher(m Matcher) *Selection {
+	return s.wrapNodes(m.MatchAll(s.document.rootNode)...)
+}
+
+// WrapSelection wraps each element in the set of matched elements inside the
+// first element in the given Selection. The element is cloned before being
+// inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapSelection(sel *Selection) *Selection {
+	return s.wrapNodes(sel.Nodes...)
+}
+
+// WrapHtml wraps each element in the set of matched elements inside the inner-
+// most child of the given HTML.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapHtml(html string) *Selection {
+	return s.wrapNodes(parseHtml(html)...)
+}
+
+// WrapNode wraps each element in the set of matched elements inside the inner-
+// most child of the given node. The given node is copied before being inserted
+// into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapNode(n *html.Node) *Selection {
+	return s.wrapNodes(n)
+}
+
+func (s *Selection) wrapNodes(ns ...*html.Node) *Selection {
+	s.Each(func(i int, ss *Selection) {
+		ss.wrapAllNodes(ns...)
+	})
+
+	return s
+}
+
+// WrapAll wraps a single HTML structure, matched by the given selector, around
+// all elements in the set of matched elements. The matched child is cloned
+// before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapAll(selector string) *Selection {
+	return s.WrapAllMatcher(compileMatcher(selector))
+}
+
+// WrapAllMatcher wraps a single HTML structure, matched by the given Matcher,
+// around all elements in the set of matched elements. The matched child is
+// cloned before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapAllMatcher(m Matcher) *Selection {
+	return s.wrapAllNodes(m.MatchAll(s.document.rootNode)...)
+}
+
+// WrapAllSelection wraps a single HTML structure, the first node of the given
+// Selection, around all elements in the set of matched elements. The matched
+// child is cloned before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapAllSelection(sel *Selection) *Selection {
+	return s.wrapAllNodes(sel.Nodes...)
+}
+
+// WrapAllHtml wraps the given HTML structure around all elements in the set of
+// matched elements. The matched child is cloned before being inserted into the
+// document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapAllHtml(html string) *Selection {
+	return s.wrapAllNodes(parseHtml(html)...)
+}
+
+func (s *Selection) wrapAllNodes(ns ...*html.Node) *Selection {
+	if len(ns) > 0 {
+		return s.WrapAllNode(ns[0])
+	}
+	return s
+}
+
+// WrapAllNode wraps the given node around the first element in the Selection,
+// making all other nodes in the Selection children of the given node. The node
+// is cloned before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapAllNode(n *html.Node) *Selection {
+	if s.Size() == 0 {
+		return s
+	}
+
+	wrap := cloneNode(n)
+
+	first := s.Nodes[0]
+	if first.Parent != nil {
+		first.Parent.InsertBefore(wrap, first)
+		first.Parent.RemoveChild(first)
+	}
+
+	for c := getFirstChildEl(wrap); c != nil; c = getFirstChildEl(wrap) {
+		wrap = c
+	}
+
+	newSingleSelection(wrap, s.document).AppendSelection(s)
+
+	return s
+}
+
+// WrapInner wraps an HTML structure, matched by the given selector, around the
+// content of element in the set of matched elements. The matched child is
+// cloned before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapInner(selector string) *Selection {
+	return s.WrapInnerMatcher(compileMatcher(selector))
+}
+
+// WrapInnerMatcher wraps an HTML structure, matched by the given selector,
+// around the content of element in the set of matched elements. The matched
+// child is cloned before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapInnerMatcher(m Matcher) *Selection {
+	return s.wrapInnerNodes(m.MatchAll(s.document.rootNode)...)
+}
+
+// WrapInnerSelection wraps an HTML structure, matched by the given selector,
+// around the content of element in the set of matched elements. The matched
+// child is cloned before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapInnerSelection(sel *Selection) *Selection {
+	return s.wrapInnerNodes(sel.Nodes...)
+}
+
+// WrapInnerHtml wraps an HTML structure, matched by the given selector, around
+// the content of element in the set of matched elements. The matched child is
+// cloned before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapInnerHtml(html string) *Selection {
+	return s.wrapInnerNodes(parseHtml(html)...)
+}
+
+// WrapInnerNode wraps an HTML structure, matched by the given selector, around
+// the content of element in the set of matched elements. The matched child is
+// cloned before being inserted into the document.
+//
+// It returns the original set of elements.
+func (s *Selection) WrapInnerNode(n *html.Node) *Selection {
+	return s.wrapInnerNodes(n)
+}
+
+func (s *Selection) wrapInnerNodes(ns ...*html.Node) *Selection {
+	if len(ns) == 0 {
+		return s
+	}
+
+	s.Each(func(i int, s *Selection) {
+		contents := s.Contents()
+
+		if contents.Size() > 0 {
+			contents.wrapAllNodes(ns...)
+		} else {
+			s.AppendNodes(cloneNode(ns[0]))
+		}
+	})
+
+	return s
+}
+
+func parseHtml(h string) []*html.Node {
+	// Errors are only returned when the io.Reader returns any error besides
+	// EOF, but strings.Reader never will
+	nodes, err := html.ParseFragment(strings.NewReader(h), &html.Node{Type: html.ElementNode})
+	if err != nil {
+		panic("goquery: failed to parse HTML: " + err.Error())
+	}
+	return nodes
+}
+
+func setHtmlNodes(s *Selection, ns ...*html.Node) *Selection {
+	for _, n := range s.Nodes {
+		for c := n.FirstChild; c != nil; c = n.FirstChild {
+			n.RemoveChild(c)
+		}
+		for _, c := range ns {
+			n.AppendChild(cloneNode(c))
+		}
+	}
+	return s
+}
+
+// Get the first child that is an ElementNode
+func getFirstChildEl(n *html.Node) *html.Node {
+	c := n.FirstChild
+	for c != nil && c.Type != html.ElementNode {
+		c = c.NextSibling
+	}
+	return c
+}
+
+// Deep copy a slice of nodes.
+func cloneNodes(ns []*html.Node) []*html.Node {
+	cns := make([]*html.Node, 0, len(ns))
+
+	for _, n := range ns {
+		cns = append(cns, cloneNode(n))
+	}
+
+	return cns
+}
+
+// Deep copy a node. The new node has clones of all the original node's
+// children but none of its parents or siblings.
+func cloneNode(n *html.Node) *html.Node {
+	nn := &html.Node{
+		Type:     n.Type,
+		DataAtom: n.DataAtom,
+		Data:     n.Data,
+		Attr:     make([]html.Attribute, len(n.Attr)),
+	}
+
+	copy(nn.Attr, n.Attr)
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		nn.AppendChild(cloneNode(c))
+	}
+
+	return nn
+}
+
+func (s *Selection) manipulateNodes(ns []*html.Node, reverse bool,
+	f func(sn *html.Node, n *html.Node)) *Selection {
+
+	lasti := s.Size() - 1
+
+	// net.Html doesn't provide document fragments for insertion, so to get
+	// things in the correct order with After() and Prepend(), the callback
+	// needs to be called on the reverse of the nodes.
+	if reverse {
+		for i, j := 0, len(ns)-1; i < j; i, j = i+1, j-1 {
+			ns[i], ns[j] = ns[j], ns[i]
+		}
+	}
+
+	for i, sn := range s.Nodes {
+		for _, n := range ns {
+			if i != lasti {
+				f(sn, cloneNode(n))
+			} else {
+				if n.Parent != nil {
+					n.Parent.RemoveChild(n)
+				}
+				f(sn, n)
+			}
+		}
+	}
+
+	return s
+}

+ 513 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/manipulation_test.go

@@ -0,0 +1,513 @@
+package goquery
+
+import (
+	"testing"
+)
+
+const (
+	wrapHtml = "<div id=\"ins\">test string<div><p><em><b></b></em></p></div></div>"
+)
+
+func TestAfter(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").After("#nf6")
+
+	assertLength(t, doc.Find("#main #nf6").Nodes, 0)
+	assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
+	assertLength(t, doc.Find("#main + #nf6").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestAfterMany(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find(".one").After("#nf6")
+
+	assertLength(t, doc.Find("#foot #nf6").Nodes, 1)
+	assertLength(t, doc.Find("#main #nf6").Nodes, 1)
+	assertLength(t, doc.Find(".one + #nf6").Nodes, 2)
+	printSel(t, doc.Selection)
+}
+
+func TestAfterWithRemoved(t *testing.T) {
+	doc := Doc2Clone()
+	s := doc.Find("#main").Remove()
+	s.After("#nf6")
+
+	assertLength(t, s.Find("#nf6").Nodes, 0)
+	assertLength(t, doc.Find("#nf6").Nodes, 0)
+	printSel(t, doc.Selection)
+}
+
+func TestAfterSelection(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").AfterSelection(doc.Find("#nf1, #nf2"))
+
+	assertLength(t, doc.Find("#main #nf1, #main #nf2").Nodes, 0)
+	assertLength(t, doc.Find("#foot #nf1, #foot #nf2").Nodes, 0)
+	assertLength(t, doc.Find("#main + #nf1, #nf1 + #nf2").Nodes, 2)
+	printSel(t, doc.Selection)
+}
+
+func TestAfterHtml(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").AfterHtml("<strong>new node</strong>")
+
+	assertLength(t, doc.Find("#main + strong").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestAppend(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").Append("#nf6")
+
+	assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
+	assertLength(t, doc.Find("#main #nf6").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestAppendBody(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("body").Append("#nf6")
+
+	assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
+	assertLength(t, doc.Find("#main #nf6").Nodes, 0)
+	assertLength(t, doc.Find("body > #nf6").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestAppendSelection(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").AppendSelection(doc.Find("#nf1, #nf2"))
+
+	assertLength(t, doc.Find("#foot #nf1").Nodes, 0)
+	assertLength(t, doc.Find("#foot #nf2").Nodes, 0)
+	assertLength(t, doc.Find("#main #nf1").Nodes, 1)
+	assertLength(t, doc.Find("#main #nf2").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestAppendSelectionExisting(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").AppendSelection(doc.Find("#n1, #n2"))
+
+	assertClass(t, doc.Find("#main :nth-child(1)"), "three")
+	assertClass(t, doc.Find("#main :nth-child(5)"), "one")
+	assertClass(t, doc.Find("#main :nth-child(6)"), "two")
+	printSel(t, doc.Selection)
+}
+
+func TestAppendClone(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#n1").AppendSelection(doc.Find("#nf1").Clone())
+
+	assertLength(t, doc.Find("#foot #nf1").Nodes, 1)
+	assertLength(t, doc.Find("#main #nf1").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestAppendHtml(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("div").AppendHtml("<strong>new node</strong>")
+
+	assertLength(t, doc.Find("strong").Nodes, 14)
+	printSel(t, doc.Selection)
+}
+
+func TestBefore(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").Before("#nf6")
+
+	assertLength(t, doc.Find("#main #nf6").Nodes, 0)
+	assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
+	assertLength(t, doc.Find("body > #nf6:first-child").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestBeforeWithRemoved(t *testing.T) {
+	doc := Doc2Clone()
+	s := doc.Find("#main").Remove()
+	s.Before("#nf6")
+
+	assertLength(t, s.Find("#nf6").Nodes, 0)
+	assertLength(t, doc.Find("#nf6").Nodes, 0)
+	printSel(t, doc.Selection)
+}
+
+func TestBeforeSelection(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").BeforeSelection(doc.Find("#nf1, #nf2"))
+
+	assertLength(t, doc.Find("#main #nf1, #main #nf2").Nodes, 0)
+	assertLength(t, doc.Find("#foot #nf1, #foot #nf2").Nodes, 0)
+	assertLength(t, doc.Find("body > #nf1:first-child, #nf1 + #nf2").Nodes, 2)
+	printSel(t, doc.Selection)
+}
+
+func TestBeforeHtml(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").BeforeHtml("<strong>new node</strong>")
+
+	assertLength(t, doc.Find("body > strong:first-child").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestEmpty(t *testing.T) {
+	doc := Doc2Clone()
+	s := doc.Find("#main").Empty()
+
+	assertLength(t, doc.Find("#main").Children().Nodes, 0)
+	assertLength(t, s.Filter("div").Nodes, 6)
+	printSel(t, doc.Selection)
+}
+
+func TestPrepend(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").Prepend("#nf6")
+
+	assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
+	assertLength(t, doc.Find("#main #nf6:first-child").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestPrependBody(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("body").Prepend("#nf6")
+
+	assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
+	assertLength(t, doc.Find("#main #nf6").Nodes, 0)
+	assertLength(t, doc.Find("body > #nf6:first-child").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestPrependSelection(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").PrependSelection(doc.Find("#nf1, #nf2"))
+
+	assertLength(t, doc.Find("#foot #nf1").Nodes, 0)
+	assertLength(t, doc.Find("#foot #nf2").Nodes, 0)
+	assertLength(t, doc.Find("#main #nf1:first-child").Nodes, 1)
+	assertLength(t, doc.Find("#main #nf2:nth-child(2)").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestPrependSelectionExisting(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main").PrependSelection(doc.Find("#n5, #n6"))
+
+	assertClass(t, doc.Find("#main :nth-child(1)"), "five")
+	assertClass(t, doc.Find("#main :nth-child(2)"), "six")
+	assertClass(t, doc.Find("#main :nth-child(5)"), "three")
+	assertClass(t, doc.Find("#main :nth-child(6)"), "four")
+	printSel(t, doc.Selection)
+}
+
+func TestPrependClone(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#n1").PrependSelection(doc.Find("#nf1").Clone())
+
+	assertLength(t, doc.Find("#foot #nf1:first-child").Nodes, 1)
+	assertLength(t, doc.Find("#main #nf1:first-child").Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestPrependHtml(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("div").PrependHtml("<strong>new node</strong>")
+
+	assertLength(t, doc.Find("strong:first-child").Nodes, 14)
+	printSel(t, doc.Selection)
+}
+
+func TestRemove(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#nf1").Remove()
+
+	assertLength(t, doc.Find("#foot #nf1").Nodes, 0)
+	printSel(t, doc.Selection)
+}
+
+func TestRemoveAll(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("*").Remove()
+
+	assertLength(t, doc.Find("*").Nodes, 0)
+	printSel(t, doc.Selection)
+}
+
+func TestRemoveRoot(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("html").Remove()
+
+	assertLength(t, doc.Find("html").Nodes, 0)
+	printSel(t, doc.Selection)
+}
+
+func TestRemoveFiltered(t *testing.T) {
+	doc := Doc2Clone()
+	nf6 := doc.Find("#nf6")
+	s := doc.Find("div").RemoveFiltered("#nf6")
+
+	assertLength(t, doc.Find("#nf6").Nodes, 0)
+	assertLength(t, s.Nodes, 1)
+	if nf6.Nodes[0] != s.Nodes[0] {
+		t.Error("Removed node does not match original")
+	}
+	printSel(t, doc.Selection)
+}
+
+func TestReplaceWith(t *testing.T) {
+	doc := Doc2Clone()
+
+	doc.Find("#nf6").ReplaceWith("#main")
+	assertLength(t, doc.Find("#foot #main:last-child").Nodes, 1)
+	printSel(t, doc.Selection)
+
+	doc.Find("#foot").ReplaceWith("#main")
+	assertLength(t, doc.Find("#foot").Nodes, 0)
+	assertLength(t, doc.Find("#main").Nodes, 1)
+
+	printSel(t, doc.Selection)
+}
+
+func TestReplaceWithHtml(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#main, #foot").ReplaceWithHtml("<div id=\"replace\"></div>")
+
+	assertLength(t, doc.Find("#replace").Nodes, 2)
+
+	printSel(t, doc.Selection)
+}
+
+func TestSetHtml(t *testing.T) {
+	doc := Doc2Clone()
+	q := doc.Find("#main, #foot")
+	q.SetHtml(`<div id="replace">test</div>`)
+
+	assertLength(t, doc.Find("#replace").Nodes, 2)
+	assertLength(t, doc.Find("#main, #foot").Nodes, 2)
+
+	if q.Text() != "testtest" {
+		t.Errorf("Expected text to be %v, found %v", "testtest", q.Text())
+	}
+
+	printSel(t, doc.Selection)
+}
+
+func TestSetHtmlNoMatch(t *testing.T) {
+	doc := Doc2Clone()
+	q := doc.Find("#notthere")
+	q.SetHtml(`<div id="replace">test</div>`)
+
+	assertLength(t, doc.Find("#replace").Nodes, 0)
+
+	printSel(t, doc.Selection)
+}
+
+func TestSetHtmlEmpty(t *testing.T) {
+	doc := Doc2Clone()
+	q := doc.Find("#main")
+	q.SetHtml(``)
+
+	assertLength(t, doc.Find("#main").Nodes, 1)
+	assertLength(t, doc.Find("#main").Children().Nodes, 0)
+	printSel(t, doc.Selection)
+}
+
+func TestSetText(t *testing.T) {
+	doc := Doc2Clone()
+	q := doc.Find("#main, #foot")
+	repl := "<div id=\"replace\">test</div>"
+	q.SetText(repl)
+
+	assertLength(t, doc.Find("#replace").Nodes, 0)
+	assertLength(t, doc.Find("#main, #foot").Nodes, 2)
+
+	if q.Text() != (repl + repl) {
+		t.Errorf("Expected text to be %v, found %v", (repl + repl), q.Text())
+	}
+
+	h, err := q.Html()
+	if err != nil {
+		t.Errorf("Error: %v", err)
+	}
+	esc := "&lt;div id=&#34;replace&#34;&gt;test&lt;/div&gt;"
+	if h != esc {
+		t.Errorf("Expected html to be %v, found %v", esc, h)
+	}
+
+	printSel(t, doc.Selection)
+}
+
+func TestReplaceWithSelection(t *testing.T) {
+	doc := Doc2Clone()
+	sel := doc.Find("#nf6").ReplaceWithSelection(doc.Find("#nf5"))
+
+	assertSelectionIs(t, sel, "#nf6")
+	assertLength(t, doc.Find("#nf6").Nodes, 0)
+	assertLength(t, doc.Find("#nf5").Nodes, 1)
+
+	printSel(t, doc.Selection)
+}
+
+func TestUnwrap(t *testing.T) {
+	doc := Doc2Clone()
+
+	doc.Find("#nf5").Unwrap()
+	assertLength(t, doc.Find("#foot").Nodes, 0)
+	assertLength(t, doc.Find("body > #nf1").Nodes, 1)
+	assertLength(t, doc.Find("body > #nf5").Nodes, 1)
+
+	printSel(t, doc.Selection)
+
+	doc = Doc2Clone()
+
+	doc.Find("#nf5, #n1").Unwrap()
+	assertLength(t, doc.Find("#foot").Nodes, 0)
+	assertLength(t, doc.Find("#main").Nodes, 0)
+	assertLength(t, doc.Find("body > #n1").Nodes, 1)
+	assertLength(t, doc.Find("body > #nf5").Nodes, 1)
+
+	printSel(t, doc.Selection)
+}
+
+func TestUnwrapBody(t *testing.T) {
+	doc := Doc2Clone()
+
+	doc.Find("#main").Unwrap()
+	assertLength(t, doc.Find("body").Nodes, 1)
+	assertLength(t, doc.Find("body > #main").Nodes, 1)
+
+	printSel(t, doc.Selection)
+}
+
+func TestUnwrapHead(t *testing.T) {
+	doc := Doc2Clone()
+
+	doc.Find("title").Unwrap()
+	assertLength(t, doc.Find("head").Nodes, 0)
+	assertLength(t, doc.Find("head > title").Nodes, 0)
+	assertLength(t, doc.Find("title").Nodes, 1)
+
+	printSel(t, doc.Selection)
+}
+
+func TestUnwrapHtml(t *testing.T) {
+	doc := Doc2Clone()
+
+	doc.Find("head").Unwrap()
+	assertLength(t, doc.Find("html").Nodes, 0)
+	assertLength(t, doc.Find("html head").Nodes, 0)
+	assertLength(t, doc.Find("head").Nodes, 1)
+
+	printSel(t, doc.Selection)
+}
+
+func TestWrap(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#nf1").Wrap("#nf2")
+	nf1 := doc.Find("#foot #nf2 #nf1")
+	assertLength(t, nf1.Nodes, 1)
+
+	nf2 := doc.Find("#nf2")
+	assertLength(t, nf2.Nodes, 2)
+
+	printSel(t, doc.Selection)
+}
+
+func TestWrapEmpty(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#nf1").Wrap("#doesnt-exist")
+
+	origHtml, _ := Doc2().Html()
+	newHtml, _ := doc.Html()
+
+	if origHtml != newHtml {
+		t.Error("Expected the two documents to be identical.")
+	}
+
+	printSel(t, doc.Selection)
+}
+
+func TestWrapHtml(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find(".odd").WrapHtml(wrapHtml)
+	nf2 := doc.Find("#ins #nf2")
+	assertLength(t, nf2.Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestWrapSelection(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#nf1").WrapSelection(doc.Find("#nf2"))
+	nf1 := doc.Find("#foot #nf2 #nf1")
+	assertLength(t, nf1.Nodes, 1)
+
+	nf2 := doc.Find("#nf2")
+	assertLength(t, nf2.Nodes, 2)
+
+	printSel(t, doc.Selection)
+}
+
+func TestWrapAll(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find(".odd").WrapAll("#nf1")
+	nf1 := doc.Find("#main #nf1")
+	assertLength(t, nf1.Nodes, 1)
+
+	sel := nf1.Find("#n2 ~ #n4 ~ #n6 ~ #nf2 ~ #nf4 ~ #nf6")
+	assertLength(t, sel.Nodes, 1)
+
+	printSel(t, doc.Selection)
+}
+
+func TestWrapAllHtml(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find(".odd").WrapAllHtml(wrapHtml)
+	nf1 := doc.Find("#main div#ins div p em b #n2 ~ #n4 ~ #n6 ~ #nf2 ~ #nf4 ~ #nf6")
+	assertLength(t, nf1.Nodes, 1)
+	printSel(t, doc.Selection)
+}
+
+func TestWrapInnerNoContent(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find(".one").WrapInner(".two")
+
+	twos := doc.Find(".two")
+	assertLength(t, twos.Nodes, 4)
+	assertLength(t, doc.Find(".one .two").Nodes, 2)
+
+	printSel(t, doc.Selection)
+}
+
+func TestWrapInnerWithContent(t *testing.T) {
+	doc := Doc3Clone()
+	doc.Find(".one").WrapInner(".two")
+
+	twos := doc.Find(".two")
+	assertLength(t, twos.Nodes, 4)
+	assertLength(t, doc.Find(".one .two").Nodes, 2)
+
+	printSel(t, doc.Selection)
+}
+
+func TestWrapInnerNoWrapper(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find(".one").WrapInner(".not-exist")
+
+	twos := doc.Find(".two")
+	assertLength(t, twos.Nodes, 2)
+	assertLength(t, doc.Find(".one").Nodes, 2)
+	assertLength(t, doc.Find(".one .two").Nodes, 0)
+
+	printSel(t, doc.Selection)
+}
+
+func TestWrapInnerHtml(t *testing.T) {
+	doc := Doc2Clone()
+	doc.Find("#foot").WrapInnerHtml(wrapHtml)
+
+	foot := doc.Find("#foot div#ins div p em b #nf1 ~ #nf2 ~ #nf3")
+	assertLength(t, foot.Nodes, 1)
+
+	printSel(t, doc.Selection)
+}

+ 37 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/misc/git/pre-commit

@@ -0,0 +1,37 @@
+#!/bin/sh
+
+echo ">>> golint"
+for dir in $(go list ./... | grep -v /vendor/)
+do
+    golint "${dir}"
+done
+echo "<<< golint"
+echo
+
+echo ">>> go vet"
+go vet $(go list ./... | grep -v /vendor/)
+echo "<<< go vet"
+echo
+
+echo ">>> gosimple"
+gosimple $(go list ./... | grep -v /vendor/)
+echo "<<< gosimple"
+echo
+
+# Check for gofmt problems and report if any.
+gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$' | grep -v /vendor/)
+[ -z "$gofiles" ] && echo "EXIT $vetres" && exit $vetres
+
+if [ -n "$gofiles" ]; then
+    unformatted=$(gofmt -l $gofiles)
+
+    if [ -n "$unformatted" ]; then
+        # Some files are not gofmt'd.
+        echo >&2 "Go files must be formatted with gofmt. Please run:"
+        for fn in $unformatted; do
+            echo >&2 "  gofmt -w $PWD/$fn"
+        done
+    fi
+fi
+echo
+

+ 275 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/property.go

@@ -0,0 +1,275 @@
+package goquery
+
+import (
+	"bytes"
+	"regexp"
+	"strings"
+
+	"golang.org/x/net/html"
+)
+
+var rxClassTrim = regexp.MustCompile("[\t\r\n]")
+
+// Attr gets the specified attribute's value for the first element in the
+// Selection. To get the value for each element individually, use a looping
+// construct such as Each or Map method.
+func (s *Selection) Attr(attrName string) (val string, exists bool) {
+	if len(s.Nodes) == 0 {
+		return
+	}
+	return getAttributeValue(attrName, s.Nodes[0])
+}
+
+// AttrOr works like Attr but returns default value if attribute is not present.
+func (s *Selection) AttrOr(attrName, defaultValue string) string {
+	if len(s.Nodes) == 0 {
+		return defaultValue
+	}
+
+	val, exists := getAttributeValue(attrName, s.Nodes[0])
+	if !exists {
+		return defaultValue
+	}
+
+	return val
+}
+
+// RemoveAttr removes the named attribute from each element in the set of matched elements.
+func (s *Selection) RemoveAttr(attrName string) *Selection {
+	for _, n := range s.Nodes {
+		removeAttr(n, attrName)
+	}
+
+	return s
+}
+
+// SetAttr sets the given attribute on each element in the set of matched elements.
+func (s *Selection) SetAttr(attrName, val string) *Selection {
+	for _, n := range s.Nodes {
+		attr := getAttributePtr(attrName, n)
+		if attr == nil {
+			n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val})
+		} else {
+			attr.Val = val
+		}
+	}
+
+	return s
+}
+
+// Text gets the combined text contents of each element in the set of matched
+// elements, including their descendants.
+func (s *Selection) Text() string {
+	var buf bytes.Buffer
+
+	// Slightly optimized vs calling Each: no single selection object created
+	var f func(*html.Node)
+	f = func(n *html.Node) {
+		if n.Type == html.TextNode {
+			// Keep newlines and spaces, like jQuery
+			buf.WriteString(n.Data)
+		}
+		if n.FirstChild != nil {
+			for c := n.FirstChild; c != nil; c = c.NextSibling {
+				f(c)
+			}
+		}
+	}
+	for _, n := range s.Nodes {
+		f(n)
+	}
+
+	return buf.String()
+}
+
+// Size is an alias for Length.
+func (s *Selection) Size() int {
+	return s.Length()
+}
+
+// Length returns the number of elements in the Selection object.
+func (s *Selection) Length() int {
+	return len(s.Nodes)
+}
+
+// Html gets the HTML contents of the first element in the set of matched
+// elements. It includes text and comment nodes.
+func (s *Selection) Html() (ret string, e error) {
+	// Since there is no .innerHtml, the HTML content must be re-created from
+	// the nodes using html.Render.
+	var buf bytes.Buffer
+
+	if len(s.Nodes) > 0 {
+		for c := s.Nodes[0].FirstChild; c != nil; c = c.NextSibling {
+			e = html.Render(&buf, c)
+			if e != nil {
+				return
+			}
+		}
+		ret = buf.String()
+	}
+
+	return
+}
+
+// AddClass adds the given class(es) to each element in the set of matched elements.
+// Multiple class names can be specified, separated by a space or via multiple arguments.
+func (s *Selection) AddClass(class ...string) *Selection {
+	classStr := strings.TrimSpace(strings.Join(class, " "))
+
+	if classStr == "" {
+		return s
+	}
+
+	tcls := getClassesSlice(classStr)
+	for _, n := range s.Nodes {
+		curClasses, attr := getClassesAndAttr(n, true)
+		for _, newClass := range tcls {
+			if !strings.Contains(curClasses, " "+newClass+" ") {
+				curClasses += newClass + " "
+			}
+		}
+
+		setClasses(n, attr, curClasses)
+	}
+
+	return s
+}
+
+// HasClass determines whether any of the matched elements are assigned the
+// given class.
+func (s *Selection) HasClass(class string) bool {
+	class = " " + class + " "
+	for _, n := range s.Nodes {
+		classes, _ := getClassesAndAttr(n, false)
+		if strings.Contains(classes, class) {
+			return true
+		}
+	}
+	return false
+}
+
+// RemoveClass removes the given class(es) from each element in the set of matched elements.
+// Multiple class names can be specified, separated by a space or via multiple arguments.
+// If no class name is provided, all classes are removed.
+func (s *Selection) RemoveClass(class ...string) *Selection {
+	var rclasses []string
+
+	classStr := strings.TrimSpace(strings.Join(class, " "))
+	remove := classStr == ""
+
+	if !remove {
+		rclasses = getClassesSlice(classStr)
+	}
+
+	for _, n := range s.Nodes {
+		if remove {
+			removeAttr(n, "class")
+		} else {
+			classes, attr := getClassesAndAttr(n, true)
+			for _, rcl := range rclasses {
+				classes = strings.Replace(classes, " "+rcl+" ", " ", -1)
+			}
+
+			setClasses(n, attr, classes)
+		}
+	}
+
+	return s
+}
+
+// ToggleClass adds or removes the given class(es) for each element in the set of matched elements.
+// Multiple class names can be specified, separated by a space or via multiple arguments.
+func (s *Selection) ToggleClass(class ...string) *Selection {
+	classStr := strings.TrimSpace(strings.Join(class, " "))
+
+	if classStr == "" {
+		return s
+	}
+
+	tcls := getClassesSlice(classStr)
+
+	for _, n := range s.Nodes {
+		classes, attr := getClassesAndAttr(n, true)
+		for _, tcl := range tcls {
+			if strings.Contains(classes, " "+tcl+" ") {
+				classes = strings.Replace(classes, " "+tcl+" ", " ", -1)
+			} else {
+				classes += tcl + " "
+			}
+		}
+
+		setClasses(n, attr, classes)
+	}
+
+	return s
+}
+
+func getAttributePtr(attrName string, n *html.Node) *html.Attribute {
+	if n == nil {
+		return nil
+	}
+
+	for i, a := range n.Attr {
+		if a.Key == attrName {
+			return &n.Attr[i]
+		}
+	}
+	return nil
+}
+
+// Private function to get the specified attribute's value from a node.
+func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) {
+	if a := getAttributePtr(attrName, n); a != nil {
+		val = a.Val
+		exists = true
+	}
+	return
+}
+
+// Get and normalize the "class" attribute from the node.
+func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) {
+	// Applies only to element nodes
+	if n.Type == html.ElementNode {
+		attr = getAttributePtr("class", n)
+		if attr == nil && create {
+			n.Attr = append(n.Attr, html.Attribute{
+				Key: "class",
+				Val: "",
+			})
+			attr = &n.Attr[len(n.Attr)-1]
+		}
+	}
+
+	if attr == nil {
+		classes = " "
+	} else {
+		classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ")
+	}
+
+	return
+}
+
+func getClassesSlice(classes string) []string {
+	return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ")
+}
+
+func removeAttr(n *html.Node, attrName string) {
+	for i, a := range n.Attr {
+		if a.Key == attrName {
+			n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr =
+				n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1]
+			return
+		}
+	}
+}
+
+func setClasses(n *html.Node, attr *html.Attribute, classes string) {
+	classes = strings.TrimSpace(classes)
+	if classes == "" {
+		removeAttr(n, "class")
+		return
+	}
+
+	attr.Val = classes
+}

+ 252 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/property_test.go

@@ -0,0 +1,252 @@
+package goquery
+
+import (
+	"regexp"
+	"strings"
+	"testing"
+)
+
+func TestAttrExists(t *testing.T) {
+	if val, ok := Doc().Find("a").Attr("href"); !ok {
+		t.Error("Expected a value for the href attribute.")
+	} else {
+		t.Logf("Href of first anchor: %v.", val)
+	}
+}
+
+func TestAttrOr(t *testing.T) {
+	if val := Doc().Find("a").AttrOr("fake-attribute", "alternative"); val != "alternative" {
+		t.Error("Expected an alternative value for 'fake-attribute' attribute.")
+	} else {
+		t.Logf("Value returned for not existing attribute: %v.", val)
+	}
+	if val := Doc().Find("zz").AttrOr("fake-attribute", "alternative"); val != "alternative" {
+		t.Error("Expected an alternative value for 'fake-attribute' on an empty selection.")
+	} else {
+		t.Logf("Value returned for empty selection: %v.", val)
+	}
+}
+
+func TestAttrNotExist(t *testing.T) {
+	if val, ok := Doc().Find("div.row-fluid").Attr("href"); ok {
+		t.Errorf("Expected no value for the href attribute, got %v.", val)
+	}
+}
+
+func TestRemoveAttr(t *testing.T) {
+	sel := Doc2Clone().Find("div")
+
+	sel.RemoveAttr("id")
+
+	_, ok := sel.Attr("id")
+	if ok {
+		t.Error("Expected there to be no id attributes set")
+	}
+}
+
+func TestSetAttr(t *testing.T) {
+	sel := Doc2Clone().Find("#main")
+
+	sel.SetAttr("id", "not-main")
+
+	val, ok := sel.Attr("id")
+	if !ok {
+		t.Error("Expected an id attribute on main")
+	}
+
+	if val != "not-main" {
+		t.Errorf("Expected an attribute id to be not-main, got %s", val)
+	}
+}
+
+func TestSetAttr2(t *testing.T) {
+	sel := Doc2Clone().Find("#main")
+
+	sel.SetAttr("foo", "bar")
+
+	val, ok := sel.Attr("foo")
+	if !ok {
+		t.Error("Expected an 'foo' attribute on main")
+	}
+
+	if val != "bar" {
+		t.Errorf("Expected an attribute 'foo' to be 'bar', got '%s'", val)
+	}
+}
+
+func TestText(t *testing.T) {
+	txt := Doc().Find("h1").Text()
+	if strings.Trim(txt, " \n\r\t") != "Provok.in" {
+		t.Errorf("Expected text to be Provok.in, found %s.", txt)
+	}
+}
+
+func TestText2(t *testing.T) {
+	txt := Doc().Find(".hero-unit .container-fluid .row-fluid:nth-child(1)").Text()
+	if ok, e := regexp.MatchString(`^\s+Provok\.in\s+Prove your point.\s+$`, txt); !ok || e != nil {
+		t.Errorf("Expected text to be Provok.in Prove your point., found %s.", txt)
+		if e != nil {
+			t.Logf("Error: %s.", e.Error())
+		}
+	}
+}
+
+func TestText3(t *testing.T) {
+	txt := Doc().Find(".pvk-gutter").First().Text()
+	// There's an &nbsp; character in there...
+	if ok, e := regexp.MatchString(`^[\s\x{00A0}]+$`, txt); !ok || e != nil {
+		t.Errorf("Expected spaces, found <%v>.", txt)
+		if e != nil {
+			t.Logf("Error: %s.", e.Error())
+		}
+	}
+}
+
+func TestHtml(t *testing.T) {
+	txt, e := Doc().Find("h1").Html()
+	if e != nil {
+		t.Errorf("Error: %s.", e)
+	}
+
+	if ok, e := regexp.MatchString(`^\s*<a href="/">Provok<span class="green">\.</span><span class="red">i</span>n</a>\s*$`, txt); !ok || e != nil {
+		t.Errorf("Unexpected HTML content, found %s.", txt)
+		if e != nil {
+			t.Logf("Error: %s.", e.Error())
+		}
+	}
+}
+
+func TestNbsp(t *testing.T) {
+	src := `<p>Some&nbsp;text</p>`
+	d, err := NewDocumentFromReader(strings.NewReader(src))
+	if err != nil {
+		t.Fatal(err)
+	}
+	txt := d.Find("p").Text()
+	ix := strings.Index(txt, "\u00a0")
+	if ix != 4 {
+		t.Errorf("Text: expected a non-breaking space at index 4, got %d", ix)
+	}
+
+	h, err := d.Find("p").Html()
+	if err != nil {
+		t.Fatal(err)
+	}
+	ix = strings.Index(h, "\u00a0")
+	if ix != 4 {
+		t.Errorf("Html: expected a non-breaking space at index 4, got %d", ix)
+	}
+}
+
+func TestAddClass(t *testing.T) {
+	sel := Doc2Clone().Find("#main")
+	sel.AddClass("main main main")
+
+	// Make sure that class was only added once
+	if a, ok := sel.Attr("class"); !ok || a != "main" {
+		t.Error("Expected #main to have class main")
+	}
+}
+
+func TestAddClassSimilar(t *testing.T) {
+	sel := Doc2Clone().Find("#nf5")
+	sel.AddClass("odd")
+
+	assertClass(t, sel, "odd")
+	assertClass(t, sel, "odder")
+	printSel(t, sel.Parent())
+}
+
+func TestAddEmptyClass(t *testing.T) {
+	sel := Doc2Clone().Find("#main")
+	sel.AddClass("")
+
+	// Make sure that class was only added once
+	if a, ok := sel.Attr("class"); ok {
+		t.Errorf("Expected #main to not to have a class, have: %s", a)
+	}
+}
+
+func TestAddClasses(t *testing.T) {
+	sel := Doc2Clone().Find("#main")
+	sel.AddClass("a b")
+
+	// Make sure that class was only added once
+	if !sel.HasClass("a") || !sel.HasClass("b") {
+		t.Errorf("#main does not have classes")
+	}
+}
+
+func TestHasClass(t *testing.T) {
+	sel := Doc().Find("div")
+	if !sel.HasClass("span12") {
+		t.Error("Expected at least one div to have class span12.")
+	}
+}
+
+func TestHasClassNone(t *testing.T) {
+	sel := Doc().Find("h2")
+	if sel.HasClass("toto") {
+		t.Error("Expected h1 to have no class.")
+	}
+}
+
+func TestHasClassNotFirst(t *testing.T) {
+	sel := Doc().Find(".alert")
+	if !sel.HasClass("alert-error") {
+		t.Error("Expected .alert to also have class .alert-error.")
+	}
+}
+
+func TestRemoveClass(t *testing.T) {
+	sel := Doc2Clone().Find("#nf1")
+	sel.RemoveClass("one row")
+
+	if !sel.HasClass("even") || sel.HasClass("one") || sel.HasClass("row") {
+		classes, _ := sel.Attr("class")
+		t.Error("Expected #nf1 to have class even, has ", classes)
+	}
+}
+
+func TestRemoveClassSimilar(t *testing.T) {
+	sel := Doc2Clone().Find("#nf5, #nf6")
+	assertLength(t, sel.Nodes, 2)
+
+	sel.RemoveClass("odd")
+	assertClass(t, sel.Eq(0), "odder")
+	printSel(t, sel)
+}
+
+func TestRemoveAllClasses(t *testing.T) {
+	sel := Doc2Clone().Find("#nf1")
+	sel.RemoveClass()
+
+	if a, ok := sel.Attr("class"); ok {
+		t.Error("All classes were not removed, has ", a)
+	}
+
+	sel = Doc2Clone().Find("#main")
+	sel.RemoveClass()
+	if a, ok := sel.Attr("class"); ok {
+		t.Error("All classes were not removed, has ", a)
+	}
+}
+
+func TestToggleClass(t *testing.T) {
+	sel := Doc2Clone().Find("#nf1")
+
+	sel.ToggleClass("one")
+	if sel.HasClass("one") {
+		t.Error("Expected #nf1 to not have class one")
+	}
+
+	sel.ToggleClass("one")
+	if !sel.HasClass("one") {
+		t.Error("Expected #nf1 to have class one")
+	}
+
+	sel.ToggleClass("one even row")
+	if a, ok := sel.Attr("class"); ok {
+		t.Errorf("Expected #nf1 to have no classes, have %q", a)
+	}
+}

+ 49 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/query.go

@@ -0,0 +1,49 @@
+package goquery
+
+import "golang.org/x/net/html"
+
+// Is checks the current matched set of elements against a selector and
+// returns true if at least one of these elements matches.
+func (s *Selection) Is(selector string) bool {
+	return s.IsMatcher(compileMatcher(selector))
+}
+
+// IsMatcher checks the current matched set of elements against a matcher and
+// returns true if at least one of these elements matches.
+func (s *Selection) IsMatcher(m Matcher) bool {
+	if len(s.Nodes) > 0 {
+		if len(s.Nodes) == 1 {
+			return m.Match(s.Nodes[0])
+		}
+		return len(m.Filter(s.Nodes)) > 0
+	}
+
+	return false
+}
+
+// IsFunction checks the current matched set of elements against a predicate and
+// returns true if at least one of these elements matches.
+func (s *Selection) IsFunction(f func(int, *Selection) bool) bool {
+	return s.FilterFunction(f).Length() > 0
+}
+
+// IsSelection checks the current matched set of elements against a Selection object
+// and returns true if at least one of these elements matches.
+func (s *Selection) IsSelection(sel *Selection) bool {
+	return s.FilterSelection(sel).Length() > 0
+}
+
+// IsNodes checks the current matched set of elements against the specified nodes
+// and returns true if at least one of these elements matches.
+func (s *Selection) IsNodes(nodes ...*html.Node) bool {
+	return s.FilterNodes(nodes...).Length() > 0
+}
+
+// Contains returns true if the specified Node is within,
+// at any depth, one of the nodes in the Selection object.
+// It is NOT inclusive, to behave like jQuery's implementation, and
+// unlike Javascript's .contains, so if the contained
+// node is itself in the selection, it returns false.
+func (s *Selection) Contains(n *html.Node) bool {
+	return sliceContains(s.Nodes, n)
+}

+ 103 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/query_test.go

@@ -0,0 +1,103 @@
+package goquery
+
+import (
+	"testing"
+)
+
+func TestIs(t *testing.T) {
+	sel := Doc().Find(".footer p:nth-child(1)")
+	if !sel.Is("p") {
+		t.Error("Expected .footer p:nth-child(1) to be p.")
+	}
+}
+
+func TestIsInvalid(t *testing.T) {
+	sel := Doc().Find(".footer p:nth-child(1)")
+	if sel.Is("") {
+		t.Error("Is should not succeed with invalid selector string")
+	}
+}
+
+func TestIsPositional(t *testing.T) {
+	sel := Doc().Find(".footer p:nth-child(2)")
+	if !sel.Is("p:nth-child(2)") {
+		t.Error("Expected .footer p:nth-child(2) to be p:nth-child(2).")
+	}
+}
+
+func TestIsPositionalNot(t *testing.T) {
+	sel := Doc().Find(".footer p:nth-child(1)")
+	if sel.Is("p:nth-child(2)") {
+		t.Error("Expected .footer p:nth-child(1) NOT to be p:nth-child(2).")
+	}
+}
+
+func TestIsFunction(t *testing.T) {
+	ok := Doc().Find("div").IsFunction(func(i int, s *Selection) bool {
+		return s.HasClass("container-fluid")
+	})
+
+	if !ok {
+		t.Error("Expected some div to have a container-fluid class.")
+	}
+}
+
+func TestIsFunctionRollback(t *testing.T) {
+	ok := Doc().Find("div").IsFunction(func(i int, s *Selection) bool {
+		return s.HasClass("container-fluid")
+	})
+
+	if !ok {
+		t.Error("Expected some div to have a container-fluid class.")
+	}
+}
+
+func TestIsSelection(t *testing.T) {
+	sel := Doc().Find("div")
+	sel2 := Doc().Find(".pvk-gutter")
+
+	if !sel.IsSelection(sel2) {
+		t.Error("Expected some div to have a pvk-gutter class.")
+	}
+}
+
+func TestIsSelectionNot(t *testing.T) {
+	sel := Doc().Find("div")
+	sel2 := Doc().Find("a")
+
+	if sel.IsSelection(sel2) {
+		t.Error("Expected some div NOT to be an anchor.")
+	}
+}
+
+func TestIsNodes(t *testing.T) {
+	sel := Doc().Find("div")
+	sel2 := Doc().Find(".footer")
+
+	if !sel.IsNodes(sel2.Nodes[0]) {
+		t.Error("Expected some div to have a footer class.")
+	}
+}
+
+func TestDocContains(t *testing.T) {
+	sel := Doc().Find("h1")
+	if !Doc().Contains(sel.Nodes[0]) {
+		t.Error("Expected document to contain H1 tag.")
+	}
+}
+
+func TestSelContains(t *testing.T) {
+	sel := Doc().Find(".row-fluid")
+	sel2 := Doc().Find("a[ng-click]")
+	if !sel.Contains(sel2.Nodes[0]) {
+		t.Error("Expected .row-fluid to contain a[ng-click] tag.")
+	}
+}
+
+func TestSelNotContains(t *testing.T) {
+	sel := Doc().Find("a.link")
+	sel2 := Doc().Find("span")
+	if sel.Contains(sel2.Nodes[0]) {
+		t.Error("Expected a.link to NOT contain span tag.")
+	}
+}

+ 855 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/testdata/gotesting.html

@@ -0,0 +1,855 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+  <title>testing - The Go Programming Language</title>
+
+<link type="text/css" rel="stylesheet" href="/doc/style.css">
+<script type="text/javascript" src="/doc/godocs.js"></script>
+
+<link rel="search" type="application/opensearchdescription+xml" title="godoc" href="/opensearch.xml" />
+
+<script type="text/javascript">
+var _gaq = _gaq || [];
+_gaq.push(["_setAccount", "UA-11222381-2"]);
+_gaq.push(["_trackPageview"]);
+</script>
+</head>
+<body>
+
+<div id="topbar"><div class="container wide">
+
+<form method="GET" action="/search">
+<div id="menu">
+<a href="/doc/">Documents</a>
+<a href="/ref/">References</a>
+<a href="/pkg/">Packages</a>
+<a href="/project/">The Project</a>
+<a href="/help/">Help</a>
+<input type="text" id="search" name="q" class="inactive" value="Search">
+</div>
+<div id="heading"><a href="/">The Go Programming Language</a></div>
+</form>
+
+</div></div>
+
+<div id="page" class="wide">
+
+
+  <div id="plusone"><g:plusone size="small" annotation="none"></g:plusone></div>
+  <h1>Package testing</h1>
+
+
+
+
+<div id="nav"></div>
+
+
+<!--
+	Copyright 2009 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.
+-->
+
+	
+		<div id="short-nav">
+			<dl>
+			<dd><code>import "testing"</code></dd>
+			</dl>
+			<dl>
+			<dd><a href="#overview" class="overviewLink">Overview</a></dd>
+			<dd><a href="#index">Index</a></dd>
+			
+			
+				<dd><a href="#subdirectories">Subdirectories</a></dd>
+			
+			</dl>
+		</div>
+		<!-- The package's Name is printed as title by the top-level template -->
+		<div id="overview" class="toggleVisible">
+			<div class="collapsed">
+				<h2 class="toggleButton" title="Click to show Overview section">Overview ▹</h2>
+			</div>
+			<div class="expanded">
+				<h2 class="toggleButton" title="Click to hide Overview section">Overview ▾</h2>
+				<p>
+Package testing provides support for automated testing of Go packages.
+It is intended to be used in concert with the &ldquo;go test&rdquo; command, which automates
+execution of any function of the form
+</p>
+<pre>func TestXxx(*testing.T)
+</pre>
+<p>
+where Xxx can be any alphanumeric string (but the first letter must not be in
+[a-z]) and serves to identify the test routine.
+These TestXxx routines should be declared within the package they are testing.
+</p>
+<p>
+Functions of the form
+</p>
+<pre>func BenchmarkXxx(*testing.B)
+</pre>
+<p>
+are considered benchmarks, and are executed by the &#34;go test&#34; command when
+the -test.bench flag is provided.
+</p>
+<p>
+A sample benchmark function looks like this:
+</p>
+<pre>func BenchmarkHello(b *testing.B) {
+    for i := 0; i &lt; b.N; i++ {
+        fmt.Sprintf(&#34;hello&#34;)
+    }
+}
+</pre>
+<p>
+The benchmark package will vary b.N until the benchmark function lasts
+long enough to be timed reliably.  The output
+</p>
+<pre>testing.BenchmarkHello    10000000    282 ns/op
+</pre>
+<p>
+means that the loop ran 10000000 times at a speed of 282 ns per loop.
+</p>
+<p>
+If a benchmark needs some expensive setup before running, the timer
+may be stopped:
+</p>
+<pre>func BenchmarkBigLen(b *testing.B) {
+    b.StopTimer()
+    big := NewBig()
+    b.StartTimer()
+    for i := 0; i &lt; b.N; i++ {
+        big.Len()
+    }
+}
+</pre>
+<p>
+The package also runs and verifies example code. Example functions may
+include a concluding comment that begins with &#34;Output:&#34; and is compared with
+the standard output of the function when the tests are run, as in these
+examples of an example:
+</p>
+<pre>func ExampleHello() {
+        fmt.Println(&#34;hello&#34;)
+        // Output: hello
+}
+
+func ExampleSalutations() {
+        fmt.Println(&#34;hello, and&#34;)
+        fmt.Println(&#34;goodbye&#34;)
+        // Output:
+        // hello, and
+        // goodbye
+}
+</pre>
+<p>
+Example functions without output comments are compiled but not executed.
+</p>
+<p>
+The naming convention to declare examples for a function F, a type T and
+method M on type T are:
+</p>
+<pre>func ExampleF() { ... }
+func ExampleT() { ... }
+func ExampleT_M() { ... }
+</pre>
+<p>
+Multiple example functions for a type/function/method may be provided by
+appending a distinct suffix to the name. The suffix must start with a
+lower-case letter.
+</p>
+<pre>func ExampleF_suffix() { ... }
+func ExampleT_suffix() { ... }
+func ExampleT_M_suffix() { ... }
+</pre>
+<p>
+The entire test file is presented as the example when it contains a single
+example function, at least one other function, type, variable, or constant
+declaration, and no test or benchmark functions.
+</p>
+
+			</div>
+		</div>
+		
+	
+		<h2 id="index">Index</h2>
+		<!-- Table of contents for API; must be named manual-nav to turn off auto nav. -->
+		<div id="manual-nav">
+			<dl>
+			
+			
+			
+				
+				<dd><a href="#Main">func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample)</a></dd>
+			
+				
+				<dd><a href="#RunBenchmarks">func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark)</a></dd>
+			
+				
+				<dd><a href="#RunExamples">func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool)</a></dd>
+			
+				
+				<dd><a href="#RunTests">func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool)</a></dd>
+			
+				
+				<dd><a href="#Short">func Short() bool</a></dd>
+			
+			
+				
+				<dd><a href="#B">type B</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.Error">func (c *B) Error(args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.Errorf">func (c *B) Errorf(format string, args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.Fail">func (c *B) Fail()</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.FailNow">func (c *B) FailNow()</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.Failed">func (c *B) Failed() bool</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.Fatal">func (c *B) Fatal(args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.Fatalf">func (c *B) Fatalf(format string, args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.Log">func (c *B) Log(args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.Logf">func (c *B) Logf(format string, args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.ResetTimer">func (b *B) ResetTimer()</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.SetBytes">func (b *B) SetBytes(n int64)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.StartTimer">func (b *B) StartTimer()</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#B.StopTimer">func (b *B) StopTimer()</a></dd>
+				
+			
+				
+				<dd><a href="#BenchmarkResult">type BenchmarkResult</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#Benchmark">func Benchmark(f func(b *B)) BenchmarkResult</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#BenchmarkResult.NsPerOp">func (r BenchmarkResult) NsPerOp() int64</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#BenchmarkResult.String">func (r BenchmarkResult) String() string</a></dd>
+				
+			
+				
+				<dd><a href="#InternalBenchmark">type InternalBenchmark</a></dd>
+				
+				
+			
+				
+				<dd><a href="#InternalExample">type InternalExample</a></dd>
+				
+				
+			
+				
+				<dd><a href="#InternalTest">type InternalTest</a></dd>
+				
+				
+			
+				
+				<dd><a href="#T">type T</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Error">func (c *T) Error(args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Errorf">func (c *T) Errorf(format string, args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Fail">func (c *T) Fail()</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.FailNow">func (c *T) FailNow()</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Failed">func (c *T) Failed() bool</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Fatal">func (c *T) Fatal(args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Fatalf">func (c *T) Fatalf(format string, args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Log">func (c *T) Log(args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Logf">func (c *T) Logf(format string, args ...interface{})</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="#T.Parallel">func (t *T) Parallel()</a></dd>
+				
+			
+			
+		</dl>
+
+		
+
+		
+			<h4>Package files</h4>
+			<p>
+			<span style="font-size:90%">
+			
+				<a href="/src/pkg/testing/benchmark.go">benchmark.go</a>
+			
+				<a href="/src/pkg/testing/example.go">example.go</a>
+			
+				<a href="/src/pkg/testing/testing.go">testing.go</a>
+			
+			</span>
+			</p>
+		
+	
+		
+		
+		
+			
+			
+			<h2 id="Main">func <a href="/src/pkg/testing/testing.go?s=9750:9890#L268">Main</a></h2>
+			<pre>func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample)</pre>
+			<p>
+An internal function but exported because it is cross-package; part of the implementation
+of the &#34;go test&#34; command.
+</p>
+
+			
+		
+			
+			
+			<h2 id="RunBenchmarks">func <a href="/src/pkg/testing/benchmark.go?s=5365:5464#L207">RunBenchmarks</a></h2>
+			<pre>func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark)</pre>
+			<p>
+An internal function but exported because it is cross-package; part of the implementation
+of the &#34;go test&#34; command.
+</p>
+
+			
+		
+			
+			
+			<h2 id="RunExamples">func <a href="/src/pkg/testing/example.go?s=314:417#L12">RunExamples</a></h2>
+			<pre>func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool)</pre>
+			
+			
+		
+			
+			
+			<h2 id="RunTests">func <a href="/src/pkg/testing/testing.go?s=10486:10580#L297">RunTests</a></h2>
+			<pre>func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool)</pre>
+			
+			
+		
+			
+			
+			<h2 id="Short">func <a href="/src/pkg/testing/testing.go?s=4859:4876#L117">Short</a></h2>
+			<pre>func Short() bool</pre>
+			<p>
+Short reports whether the -test.short flag is set.
+</p>
+
+			
+		
+		
+			
+			
+			<h2 id="B">type <a href="/src/pkg/testing/benchmark.go?s=743:872#L17">B</a></h2>
+			<pre>type B struct {
+    N int
+    <span class="comment">// contains filtered or unexported fields</span>
+}</pre>
+			<p>
+B is a type passed to Benchmark functions to manage benchmark
+timing and to specify the number of iterations to run.
+</p>
+
+
+			
+
+			
+
+			
+
+			
+
+			
+				
+				<h3 id="B.Error">func (*B) <a href="/src/pkg/testing/testing.go?s=8110:8153#L209">Error</a></h3>
+				<pre>func (c *B) Error(args ...interface{})</pre>
+				<p>
+Error is equivalent to Log() followed by Fail().
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.Errorf">func (*B) <a href="/src/pkg/testing/testing.go?s=8253:8312#L215">Errorf</a></h3>
+				<pre>func (c *B) Errorf(format string, args ...interface{})</pre>
+				<p>
+Errorf is equivalent to Logf() followed by Fail().
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.Fail">func (*B) <a href="/src/pkg/testing/testing.go?s=6270:6293#L163">Fail</a></h3>
+				<pre>func (c *B) Fail()</pre>
+				<p>
+Fail marks the function as having failed but continues execution.
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.FailNow">func (*B) <a href="/src/pkg/testing/testing.go?s=6548:6574#L170">FailNow</a></h3>
+				<pre>func (c *B) FailNow()</pre>
+				<p>
+FailNow marks the function as having failed and stops its execution.
+Execution will continue at the next test or benchmark.
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.Failed">func (*B) <a href="/src/pkg/testing/testing.go?s=6366:6396#L166">Failed</a></h3>
+				<pre>func (c *B) Failed() bool</pre>
+				<p>
+Failed returns whether the function has failed.
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.Fatal">func (*B) <a href="/src/pkg/testing/testing.go?s=8420:8463#L221">Fatal</a></h3>
+				<pre>func (c *B) Fatal(args ...interface{})</pre>
+				<p>
+Fatal is equivalent to Log() followed by FailNow().
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.Fatalf">func (*B) <a href="/src/pkg/testing/testing.go?s=8569:8628#L227">Fatalf</a></h3>
+				<pre>func (c *B) Fatalf(format string, args ...interface{})</pre>
+				<p>
+Fatalf is equivalent to Logf() followed by FailNow().
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.Log">func (*B) <a href="/src/pkg/testing/testing.go?s=7763:7804#L202">Log</a></h3>
+				<pre>func (c *B) Log(args ...interface{})</pre>
+				<p>
+Log formats its arguments using default formatting, analogous to Println(),
+and records the text in the error log.
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.Logf">func (*B) <a href="/src/pkg/testing/testing.go?s=7959:8016#L206">Logf</a></h3>
+				<pre>func (c *B) Logf(format string, args ...interface{})</pre>
+				<p>
+Logf formats its arguments according to the format, analogous to Printf(),
+and records the text in the error log.
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.ResetTimer">func (*B) <a href="/src/pkg/testing/benchmark.go?s=1503:1527#L48">ResetTimer</a></h3>
+				<pre>func (b *B) ResetTimer()</pre>
+				<p>
+ResetTimer sets the elapsed benchmark time to zero.
+It does not affect whether the timer is running.
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.SetBytes">func (*B) <a href="/src/pkg/testing/benchmark.go?s=1728:1757#L57">SetBytes</a></h3>
+				<pre>func (b *B) SetBytes(n int64)</pre>
+				<p>
+SetBytes records the number of bytes processed in a single operation.
+If this is called, the benchmark will report ns/op and MB/s.
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.StartTimer">func (*B) <a href="/src/pkg/testing/benchmark.go?s=1047:1071#L29">StartTimer</a></h3>
+				<pre>func (b *B) StartTimer()</pre>
+				<p>
+StartTimer starts timing a test.  This function is called automatically
+before a benchmark starts, but it can also used to resume timing after
+a call to StopTimer.
+</p>
+
+				
+				
+			
+				
+				<h3 id="B.StopTimer">func (*B) <a href="/src/pkg/testing/benchmark.go?s=1288:1311#L39">StopTimer</a></h3>
+				<pre>func (b *B) StopTimer()</pre>
+				<p>
+StopTimer stops timing a test.  This can be used to pause the timer
+while performing complex initialization that you don&#39;t
+want to measure.
+</p>
+
+				
+				
+			
+		
+			
+			
+			<h2 id="BenchmarkResult">type <a href="/src/pkg/testing/benchmark.go?s=4206:4391#L165">BenchmarkResult</a></h2>
+			<pre>type BenchmarkResult struct {
+    N     int           <span class="comment">// The number of iterations.</span>
+    T     time.Duration <span class="comment">// The total time taken.</span>
+    Bytes int64         <span class="comment">// Bytes processed in one iteration.</span>
+}</pre>
+			<p>
+The results of a benchmark run.
+</p>
+
+
+			
+
+			
+
+			
+
+			
+				
+				<h3 id="Benchmark">func <a href="/src/pkg/testing/benchmark.go?s=7545:7589#L275">Benchmark</a></h3>
+				<pre>func Benchmark(f func(b *B)) BenchmarkResult</pre>
+				<p>
+Benchmark benchmarks a single function. Useful for creating
+custom benchmarks that do not use the &#34;go test&#34; command.
+</p>
+
+				
+			
+
+			
+				
+				<h3 id="BenchmarkResult.NsPerOp">func (BenchmarkResult) <a href="/src/pkg/testing/benchmark.go?s=4393:4433#L171">NsPerOp</a></h3>
+				<pre>func (r BenchmarkResult) NsPerOp() int64</pre>
+				
+				
+				
+			
+				
+				<h3 id="BenchmarkResult.String">func (BenchmarkResult) <a href="/src/pkg/testing/benchmark.go?s=4677:4717#L185">String</a></h3>
+				<pre>func (r BenchmarkResult) String() string</pre>
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="InternalBenchmark">type <a href="/src/pkg/testing/benchmark.go?s=555:618#L10">InternalBenchmark</a></h2>
+			<pre>type InternalBenchmark struct {
+    Name string
+    F    func(b *B)
+}</pre>
+			<p>
+An internal type but exported because it is cross-package; part of the implementation
+of the &#34;go test&#34; command.
+</p>
+
+
+			
+
+			
+
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="InternalExample">type <a href="/src/pkg/testing/example.go?s=236:312#L6">InternalExample</a></h2>
+			<pre>type InternalExample struct {
+    Name   string
+    F      func()
+    Output string
+}</pre>
+			
+
+			
+
+			
+
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="InternalTest">type <a href="/src/pkg/testing/testing.go?s=9065:9121#L241">InternalTest</a></h2>
+			<pre>type InternalTest struct {
+    Name string
+    F    func(*T)
+}</pre>
+			<p>
+An internal type but exported because it is cross-package; part of the implementation
+of the &#34;go test&#34; command.
+</p>
+
+
+			
+
+			
+
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="T">type <a href="/src/pkg/testing/testing.go?s=6070:6199#L156">T</a></h2>
+			<pre>type T struct {
+    <span class="comment">// contains filtered or unexported fields</span>
+}</pre>
+			<p>
+T is a type passed to Test functions to manage test state and support formatted test logs.
+Logs are accumulated during execution and dumped to standard error when done.
+</p>
+
+
+			
+
+			
+
+			
+
+			
+
+			
+				
+				<h3 id="T.Error">func (*T) <a href="/src/pkg/testing/testing.go?s=8110:8153#L209">Error</a></h3>
+				<pre>func (c *T) Error(args ...interface{})</pre>
+				<p>
+Error is equivalent to Log() followed by Fail().
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.Errorf">func (*T) <a href="/src/pkg/testing/testing.go?s=8253:8312#L215">Errorf</a></h3>
+				<pre>func (c *T) Errorf(format string, args ...interface{})</pre>
+				<p>
+Errorf is equivalent to Logf() followed by Fail().
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.Fail">func (*T) <a href="/src/pkg/testing/testing.go?s=6270:6293#L163">Fail</a></h3>
+				<pre>func (c *T) Fail()</pre>
+				<p>
+Fail marks the function as having failed but continues execution.
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.FailNow">func (*T) <a href="/src/pkg/testing/testing.go?s=6548:6574#L170">FailNow</a></h3>
+				<pre>func (c *T) FailNow()</pre>
+				<p>
+FailNow marks the function as having failed and stops its execution.
+Execution will continue at the next test or benchmark.
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.Failed">func (*T) <a href="/src/pkg/testing/testing.go?s=6366:6396#L166">Failed</a></h3>
+				<pre>func (c *T) Failed() bool</pre>
+				<p>
+Failed returns whether the function has failed.
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.Fatal">func (*T) <a href="/src/pkg/testing/testing.go?s=8420:8463#L221">Fatal</a></h3>
+				<pre>func (c *T) Fatal(args ...interface{})</pre>
+				<p>
+Fatal is equivalent to Log() followed by FailNow().
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.Fatalf">func (*T) <a href="/src/pkg/testing/testing.go?s=8569:8628#L227">Fatalf</a></h3>
+				<pre>func (c *T) Fatalf(format string, args ...interface{})</pre>
+				<p>
+Fatalf is equivalent to Logf() followed by FailNow().
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.Log">func (*T) <a href="/src/pkg/testing/testing.go?s=7763:7804#L202">Log</a></h3>
+				<pre>func (c *T) Log(args ...interface{})</pre>
+				<p>
+Log formats its arguments using default formatting, analogous to Println(),
+and records the text in the error log.
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.Logf">func (*T) <a href="/src/pkg/testing/testing.go?s=7959:8016#L206">Logf</a></h3>
+				<pre>func (c *T) Logf(format string, args ...interface{})</pre>
+				<p>
+Logf formats its arguments according to the format, analogous to Printf(),
+and records the text in the error log.
+</p>
+
+				
+				
+			
+				
+				<h3 id="T.Parallel">func (*T) <a href="/src/pkg/testing/testing.go?s=8809:8831#L234">Parallel</a></h3>
+				<pre>func (t *T) Parallel()</pre>
+				<p>
+Parallel signals that this test is to be run in parallel with (and only with)
+other parallel tests in this CPU group.
+</p>
+
+				
+				
+			
+		
+		</div>
+	
+
+	
+
+
+
+
+
+
+
+	
+	
+		<h2 id="subdirectories">Subdirectories</h2>
+	
+	<table class="dir">
+	<tr>
+	<th>Name</th>
+	<th>&nbsp;&nbsp;&nbsp;&nbsp;</th>
+	<th style="text-align: left; width: auto">Synopsis</th>
+	</tr>
+	
+		<tr>
+		<td><a href="..">..</a></td>
+		</tr>
+	
+	
+		
+			<tr>
+			<td class="name"><a href="iotest">iotest</a></td>
+			<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
+			<td style="width: auto">Package iotest implements Readers and Writers useful mainly for testing.</td>
+			</tr>
+		
+	
+		
+			<tr>
+			<td class="name"><a href="quick">quick</a></td>
+			<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
+			<td style="width: auto">Package quick implements utility functions to help with black box testing.</td>
+			</tr>
+		
+	
+	</table>
+	
+
+
+
+</div>
+
+<div id="footer">
+Build version go1.0.2.<br>
+Except as <a href="http://code.google.com/policies.html#restrictions">noted</a>,
+the content of this page is licensed under the
+Creative Commons Attribution 3.0 License,
+and code is licensed under a <a href="/LICENSE">BSD license</a>.<br>
+<a href="/doc/tos.html">Terms of Service</a> | 
+<a href="http://www.google.com/intl/en/privacy/privacy-policy.html">Privacy Policy</a>
+</div>
+
+<script type="text/javascript">
+(function() {
+  var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
+  ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
+  var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
+})();
+</script>
+</body>
+<script type="text/javascript">
+  (function() {
+    var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
+    po.src = 'https://apis.google.com/js/plusone.js';
+    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
+  })();
+</script>
+</html>
+

File diff suppressed because it is too large
+ 26 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/testdata/gowiki.html


+ 413 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/testdata/metalreview.html

@@ -0,0 +1,413 @@
+
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" >
+<head><meta http-equiv="X-UA-Compatible" content="IE=8" />
+    
+<meta name="keywords" content="metal, reviews, metalreview, metalreviews, heavy, rock, review, music, blogs, forums, community" />
+<meta name="description" content="Critical heavy metal album and dvd reviews, written by professional writers. Large community with forums, blogs, photos and commenting system." />
+
+<title>
+	
+    
+	Metal Reviews, News, Blogs, Interviews and Community | Metal Review
+
+
+</title><link rel="stylesheet" type="text/css" href="/Content/Css/reset-fonts-grids.css" /><link rel="stylesheet" type="text/css" href="/Content/Css/base.css" /><link rel="stylesheet" type="text/css" href="/Content/Css/core.css" /><link rel="stylesheet" type="text/css" href="/Content/Css/wt-rotator.css" />
+    <script src="/Scripts/jquery-1.4.2.min.js" type="text/javascript"></script>
+    </head>
+<body>
+    <script type="text/javascript">
+        var _comscore = _comscore || [];
+        _comscore.push({ c1: "2", c2: "9290245" });
+        (function () {
+            var s = document.createElement("script"), el = document.getElementsByTagName("script")[0]; s.async = true;
+            s.src = (document.location.protocol == "https:" ? "https://sb" : "http://b") + ".scorecardresearch.com/beacon.js";
+            el.parentNode.insertBefore(s, el);
+        })();
+    </script>
+    <noscript>
+    <img src="http://b.scorecardresearch.com/p?c1=2&c2=9290245&cv=2.0&cj=1" />
+    </noscript>
+
+    
+<div id="doc2" class="yui-t7">
+	<div id="hd">
+		
+
+<div id="main-logo"><a href="/" title="Home"><img src="/Content/Images/metal-review-logo.png" alt="Metal Review Home" border="0" /></a></div>
+<div id="leaderboard-banner">
+
+<script language="javascript" type="text/javascript"><!--
+    document.write('<scr' + 'ipt language="javascript1.1" src="http://adserver.adtechus.com/addyn/3.0/5110/73085/0/225/ADTECH;loc=100;target=_blank;key=key1+key2+key3+key4;grp=[group];misc=' + new Date().getTime() + '"></scri' + 'pt>');
+    //-->
+</script>
+
+<noscript>
+    <a href="http://adserver.adtechus.com/adlink/3.0/5110/73085/0/225/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" target="_blank">
+        <img src="http://adserver.adtechus.com/adserv/3.0/5110/73085/0/225/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" border="0" width="728" height="90" />
+    </a>
+</noscript>
+</div>
+<div id="header-menu-container">
+    <div id="header-menu">
+        <a href="/reviews/browse">REVIEWS</a>
+        <a href="http://community2.metalreview.com/blogs/editorials/default.aspx">FEATURES</a>
+        <a href="/artists/browse">ARTISTS</a>
+        <a href="/reviews/pipeline">PIPELINE</a>
+        <a href="http://community2.metalreview.com/forums">FORUMS</a>
+        <a href="http://community2.metalreview.com/blogs/">BLOGS</a>
+        <a href="/aboutus">ABOUT US</a>
+    </div>
+    
+        <div id="sign-in"><a href="https://metalreview.com/account/signin">SIGN IN</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="/account/register">JOIN US</a></div>
+    
+</div>
+	</div>
+	<div id="bd">
+        
+		<div id="yui-main">
+			<div class="yui-b">
+				<div class="yui-g">
+					<div class="yui-u first">
+						
+     
+
+<script src="/Scripts/jquery.wt-rotator.min.js" type="text/javascript"></script>
+<script src="/Scripts/jquery.wt-rotator-initialize.js" type="text/javascript"></script>
+<div id="review-showcase-wrapper">
+    <h2 id="showcase-heading">Reviews</h2>
+    <div id="review-showcase">
+        <div class="container">
+            <div class="wt-rotator">
+                <a href="#"></a>
+                <div class="desc">
+                </div>
+                <div class="preloader">
+                </div>
+                <div class="c-panel">
+                    <div class="buttons">
+                        <div class="prev-btn">
+                        </div>
+                        <div class="play-btn">
+                        </div>
+                        <div class="next-btn">
+                        </div>
+                    </div>
+                    <div class="thumbnails">
+                        <ul>
+                              
+                             <li><a href="artist.photo?mrx=4641" title="Serpentine Path - Serpentine Path"></a><a href="/reviews/6844/serpentine-path-serpentine-path"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6844" alt='Serpentine Path - Serpentine Path' title='Serpentine Path - Serpentine Path' />
+                                    <span class="title"><strong>Serpentine Path</strong></span><br />
+                                    Serpentine Path<br />
+                                    
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=4635" title="Hunter's Ground - No God But the Wild"></a><a href="/reviews/6830/hunters-ground-no-god-but-the-wild"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6830" alt='Hunter's Ground - No God But the Wild' title='Hunter's Ground - No God But the Wild' />
+                                    <span class="title"><strong>Hunter's Ground</strong></span><br />
+                                    No God But the Wild<br />
+                                    
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=1035" title="Blut Aus Nord - 777 - Cosmosophy"></a><a href="/reviews/6829/blut-aus-nord-777---cosmosophy"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6829" alt='Blut Aus Nord - 777 - Cosmosophy' title='Blut Aus Nord - 777 - Cosmosophy' />
+                                    <span class="title"><strong>Blut Aus Nord</strong></span><br />
+                                    777 - Cosmosophy<br />
+                                    <a href="/tags/10/black"><span class="tag">Black</span></a>
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=1217" title="Ufomammut - Oro: Opus Alter"></a><a href="/reviews/6835/ufomammut-oro--opus-alter"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6835" alt='Ufomammut - Oro: Opus Alter' title='Ufomammut - Oro: Opus Alter' />
+                                    <span class="title"><strong>Ufomammut</strong></span><br />
+                                    Oro: Opus Alter<br />
+                                    <a href="/tags/2/doom"><span class="tag">Doom</span></a>
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=4590" title="Resurgency - False Enlightenment"></a><a href="/reviews/6746/resurgency-false-enlightenment"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6746" alt='Resurgency - False Enlightenment' title='Resurgency - False Enlightenment' />
+                                    <span class="title"><strong>Resurgency</strong></span><br />
+                                    False Enlightenment<br />
+                                    <a href="/tags/1/death"><span class="tag">Death</span></a>
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=1360" title="Morgoth - Cursed to Live"></a><a href="/reviews/6800/morgoth-cursed-to-live"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6800" alt='Morgoth - Cursed to Live' title='Morgoth - Cursed to Live' />
+                                    <span class="title"><strong>Morgoth</strong></span><br />
+                                    Cursed to Live<br />
+                                    <a href="/tags/1/death"><span class="tag">Death</span></a><a href="/tags/31/live"><span class="tag">Live</span></a>
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=3879" title="Krallice - Years Past Matter"></a><a href="/reviews/6853/krallice-years-past-matter"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6853" alt='Krallice - Years Past Matter' title='Krallice - Years Past Matter' />
+                                    <span class="title"><strong>Krallice</strong></span><br />
+                                    Years Past Matter<br />
+                                    <a href="/tags/10/black"><span class="tag">Black</span></a>
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=4243" title="Murder Construct - Results"></a><a href="/reviews/6782/murder-construct-results"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6782" alt='Murder Construct - Results' title='Murder Construct - Results' />
+                                    <span class="title"><strong>Murder Construct</strong></span><br />
+                                    Results<br />
+                                    <a href="/tags/13/grindcore"><span class="tag">Grindcore</span></a>
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=251" title="Grave - Endless Procession of Souls"></a><a href="/reviews/6834/grave-endless-procession-of-souls"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6834" alt='Grave - Endless Procession of Souls' title='Grave - Endless Procession of Souls' />
+                                    <span class="title"><strong>Grave</strong></span><br />
+                                    Endless Procession of Souls<br />
+                                    <a href="/tags/1/death"><span class="tag">Death</span></a>
+                                </p>
+                            </li>
+                             
+                             <li><a href="artist.photo?mrx=3508" title="Master - The New Elite"></a><a href="/reviews/6774/master-the-new-elite"></a>
+                                 <p style="top: 130px; left: 22px; width: 305px; height:60px;">
+                                    <img class="rotator-cover-art" src="album.cover?art=6774" alt='Master - The New Elite' title='Master - The New Elite' />
+                                    <span class="title"><strong>Master</strong></span><br />
+                                    The New Elite<br />
+                                    <a href="/tags/1/death"><span class="tag">Death</span></a>
+                                </p>
+                            </li>
+                            
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div id="showcase-all-artist-albums">
+        <a href="/reviews/6844/serpentine-path-serpentine-path"><img src="album.cover?art=6844" alt="Serpentine Path - Serpentine Path" /></a><a href="/reviews/6830/hunters-ground-no-god-but-the-wild"><img src="album.cover?art=6830" alt="Hunter's Ground - No God But the Wild" /></a><a href="/reviews/6829/blut-aus-nord-777---cosmosophy"><img src="album.cover?art=6829" alt="Blut Aus Nord - 777 - Cosmosophy" /></a><a href="/reviews/6835/ufomammut-oro--opus-alter"><img src="album.cover?art=6835" alt="Ufomammut - Oro: Opus Alter" /></a><a href="/reviews/6746/resurgency-false-enlightenment"><img src="album.cover?art=6746" alt="Resurgency - False Enlightenment" /></a><a href="/reviews/6800/morgoth-cursed-to-live"><img src="album.cover?art=6800" alt="Morgoth - Cursed to Live" /></a><a href="/reviews/6853/krallice-years-past-matter"><img src="album.cover?art=6853" alt="Krallice - Years Past Matter" /></a><a href="/reviews/6782/murder-construct-results"><img src="album.cover?art=6782" alt="Murder Construct - Results" /></a><a href="/reviews/6834/grave-endless-procession-of-souls"><img src="album.cover?art=6834" alt="Grave - Endless Procession of Souls" /></a><a href="/reviews/6774/master-the-new-elite"><img src="album.cover?art=6774" alt="Master - The New Elite" /></a>
+    </div>
+</div>
+					</div>
+					<div class="yui-u">
+						
+    
+
+<div id="feature-feed">
+<h2>Features</h2>
+<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/08/15/corsair-interview.aspx"><span class="feature-link"><strong>Release The SkyKrakken: Corsair Interview</strong></span></a><br /><span class="publish-date">8/15/2012 by JW</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.21.25/4TMR3E1CWERK.jpg" alt="JW's Avatar" width="36px" height="40px" border="0" /></div>
+<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/08/09/riffology-kreative-evolution-part-iii.aspx"><span class="feature-link"><strong>Riffology: Kreative Evolution, Part III</strong></span></a><br /><span class="publish-date">8/9/2012 by Achilles</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.21.44/4THUGH622I68.jpg" alt="Achilles's Avatar" width="40px" height="39px" border="0" /></div>
+<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/08/02/reverend-s-bazaar-don-t-trend-on-me.aspx"><span class="feature-link"><strong>Reverend's Bazaar - Don't Trend On Me  </strong></span></a><br /><span class="publish-date">8/2/2012 by Reverend Campbell</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.21.18/4TM06FD0ND4G.png" alt="Reverend Campbell's Avatar" width="34px" height="40px" border="0" /></div>
+<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/08/01/grand-theft-metal-three-for-free.aspx"><span class="feature-link"><strong>Grand Theft Metal - Free Four All  </strong></span></a><br /><span class="publish-date">8/2/2012 by Dave</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.22.16/4TKJCJQ00VFO.jpg" alt="Dave's Avatar" width="33px" height="40px" border="0" /></div>
+<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/07/29/monday-with-moonspell-the-interview.aspx"><span class="feature-link"><strong>A Monday with Moonspell: The Interview</strong></span></a><br /><span class="publish-date">7/29/2012 by raetamacue</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.71.26/4TLIHKLUSXF4.jpg" alt="raetamacue's Avatar" width="37px" height="40px" border="0" /></div>
+<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/07/26/riffology-kreative-evolution-part-ii.aspx"><span class="feature-link"><strong>Riffology: Kreative Evolution Part II</strong></span></a><br /><span class="publish-date">7/26/2012 by Achilles</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.21.44/4THUGH622I68.jpg" alt="Achilles's Avatar" width="40px" height="39px" border="0" /></div>
+<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/07/24/shadow-kingdom-records-giveaway.aspx"><span class="feature-link"><strong>WINNERS ANNOUNCED -- Shadow Kingdom Records Give...</strong></span></a><br /><span class="publish-date">7/24/2012 by Metal Review</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.59.06/4TFD2N58B7BS.png" alt="Metal Review's Avatar" width="34px" height="40px" border="0" /></div>
+
+<br />
+<a href="http://community2.metalreview.com/blogs/editorials/default.aspx"><strong>More Editorials</strong></a>
+</div>
+
+
+
+					</div>
+				</div>
+			</div>
+		</div>
+		<div class="yui-b">
+			
+    
+
+<script src="/Scripts/jquery.cycle.all.min.js" type="text/javascript"></script>
+<div id="slider-next-button"><img id="slider-next" src="/Content/Images/Backgrounds/rotator-next-button.png" alt="Goto Next Group" title="Goto Next Group" /></div>
+<div id="slider-back-button"><img id="slider-back" src="/Content/Images/Backgrounds/rotator-back-button.png" alt="Goto Previous Group" title="Goto Previous Group" /></div>
+<div id="latest-reviews-slider">
+<div class="slider-row">
+<div class="slider-item"><a href="/reviews/6795/midnight-complete-and-total-hell"><img src="album.cover?art=6795" alt="Midnight Complete and Total Hell" /><br /><strong>Midnight</strong><br /><em>Complete and Total Hell</em></a><div class="score">8.5</div></div>
+<div class="slider-item"><a href="/reviews/6842/over-your-threshold-facticity"><img src="album.cover?art=6842" alt="Over Your Threshold Facticity" /><br /><strong>Over Your Threshold</strong><br /><em>Facticity</em></a><div class="score">6.0</div></div>
+<div class="slider-item"><a href="/reviews/6813/nuclear-death-terror-chaos-reigns"><img src="album.cover?art=6813" alt="Nuclear Death Terror Chaos Reigns" /><br /><strong>Nuclear Death Terror</strong><br /><em>Chaos Reigns</em></a><div class="score">7.5</div></div>
+<div class="slider-item"><a href="/reviews/6811/evoken-atra-mors"><img src="album.cover?art=6811" alt="Evoken Atra Mors" /><br /><strong>Evoken</strong><br /><em>Atra Mors</em></a><div class="score">9.5</div></div>
+<div class="slider-item"><a href="/reviews/6807/blacklodge-machination"><img src="album.cover?art=6807" alt="Blacklodge MachinatioN" /><br /><strong>Blacklodge</strong><br /><em>MachinatioN</em></a><div class="score">5.5</div></div>
+<div class="slider-item"><a href="/reviews/6832/prototype-catalyst"><img src="album.cover?art=6832" alt="Prototype Catalyst" /><br /><strong>Prototype</strong><br /><em>Catalyst</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6822/hypnosia-horror-infernal"><img src="album.cover?art=6822" alt="Hypnosia Horror Infernal" /><br /><strong>Hypnosia</strong><br /><em>Horror Infernal</em></a><div class="score">7.0</div></div>
+<div class="slider-item"><a href="/reviews/6787/om-advaitic-songs"><img src="album.cover?art=6787" alt="OM Advaitic Songs" /><br /><strong>OM</strong><br /><em>Advaitic Songs</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6765/afgrund-the-age-of-dumb"><img src="album.cover?art=6765" alt="Afgrund The Age Of Dumb" /><br /><strong>Afgrund</strong><br /><em>The Age Of Dumb</em></a><div class="score">8.5</div></div>
+<div class="slider-item"><a href="/reviews/6773/binah-hallucinating-in-resurrecture"><img src="album.cover?art=6773" alt="Binah Hallucinating in Resurrecture" /><br /><strong>Binah</strong><br /><em>Hallucinating in Resurrecture</em></a><div class="score">8.5</div></div>
+</div>
+<div class="slider-row">
+<div class="slider-item"><a href="/reviews/6802/deiphago-satan-alpha-omega"><img src="album.cover?art=6802" alt="Deiphago Satan Alpha Omega" /><br /><strong>Deiphago</strong><br /><em>Satan Alpha Omega</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6719/conan-monnos"><img src="album.cover?art=6719" alt="Conan Monnos" /><br /><strong>Conan</strong><br /><em>Monnos</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6702/alaric-alaric-atriarch---split-lp"><img src="album.cover?art=6702" alt="Alaric Alaric/Atriarch - Split LP" /><br /><strong>Alaric</strong><br /><em>Alaric/Atriarch - Split LP</em></a><div class="score">8.5</div></div>
+<div class="slider-item"><a href="/reviews/6780/coven-worship-new-gods-(reissue)"><img src="album.cover?art=6780" alt="Coven Worship New Gods (Reissue)" /><br /><strong>Coven</strong><br /><em>Worship New Gods (Reissue)</em></a><div class="score">5.0</div></div>
+<div class="slider-item"><a href="/reviews/6831/the-foreshadowing-second-world"><img src="album.cover?art=6831" alt="The Foreshadowing Second World" /><br /><strong>The Foreshadowing</strong><br /><em>Second World</em></a><div class="score">5.5</div></div>
+<div class="slider-item"><a href="/reviews/6815/nether-regions-into-the-breach"><img src="album.cover?art=6815" alt="Nether Regions Into The Breach" /><br /><strong>Nether Regions</strong><br /><em>Into The Breach</em></a><div class="score">7.0</div></div>
+<div class="slider-item"><a href="/reviews/6824/agalloch-faustian-echoes"><img src="album.cover?art=6824" alt="Agalloch Faustian Echoes" /><br /><strong>Agalloch</strong><br /><em>Faustian Echoes</em></a><div class="score">9.0</div></div>
+<div class="slider-item"><a href="/reviews/6805/a-forest-of-stars-a-shadowplay-for-yesterdays"><img src="album.cover?art=6805" alt="A Forest Of Stars A Shadowplay For Yesterdays" /><br /><strong>A Forest Of Stars</strong><br /><em>A Shadowplay For Yesterdays</em></a><div class="score">9.0</div></div>
+<div class="slider-item"><a href="/reviews/6763/de-profundis-the-emptiness-within"><img src="album.cover?art=6763" alt="De Profundis The Emptiness Within" /><br /><strong>De Profundis</strong><br /><em>The Emptiness Within</em></a><div class="score">7.5</div></div>
+<div class="slider-item"><a href="/reviews/6826/ozzy-osbourne-speak-of-the-devil"><img src="album.cover?art=6826" alt="Ozzy Osbourne Speak of the Devil" /><br /><strong>Ozzy Osbourne</strong><br /><em>Speak of the Devil</em></a><div class="score">7.5</div></div>
+</div>
+<div class="slider-row">
+<div class="slider-item"><a href="/reviews/6825/testament-dark-roots-of-earth"><img src="album.cover?art=6825" alt="Testament Dark Roots of Earth" /><br /><strong>Testament</strong><br /><em>Dark Roots of Earth</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6796/eagle-twin-the-feather-tipped-the-serpents-scale"><img src="album.cover?art=6796" alt="Eagle Twin The Feather Tipped The Serpent's Scale" /><br /><strong>Eagle Twin</strong><br /><em>The Feather Tipped The Serpent's Scale</em></a><div class="score">8.5</div></div>
+<div class="slider-item"><a href="/reviews/6609/king-forged-by-satans-doctrine"><img src="album.cover?art=6609" alt="King Forged by Satan's Doctrine" /><br /><strong>King</strong><br /><em>Forged by Satan's Doctrine</em></a><div class="score">5.5</div></div>
+<div class="slider-item"><a href="/reviews/6798/khors-wisdom-of-centuries"><img src="album.cover?art=6798" alt="Khors Wisdom of Centuries" /><br /><strong>Khors</strong><br /><em>Wisdom of Centuries</em></a><div class="score">8.5</div></div>
+<div class="slider-item"><a href="/reviews/6776/samothrace-reverence-to-stone"><img src="album.cover?art=6776" alt="Samothrace Reverence To Stone" /><br /><strong>Samothrace</strong><br /><em>Reverence To Stone</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6784/horseback-on-the-eclipse"><img src="album.cover?art=6784" alt="Horseback On the Eclipse" /><br /><strong>Horseback</strong><br /><em>On the Eclipse</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6690/incoming-cerebral-overdrive-le-stelle--a-voyage-adrift"><img src="album.cover?art=6690" alt="Incoming Cerebral Overdrive Le Stelle: A Voyage Adrift" /><br /><strong>Incoming Cerebral Overdrive</strong><br /><em>Le Stelle: A Voyage Adrift</em></a><div class="score">7.5</div></div>
+<div class="slider-item"><a href="/reviews/6658/struck-by-lightning-true-predation"><img src="album.cover?art=6658" alt="Struck By Lightning True Predation" /><br /><strong>Struck By Lightning</strong><br /><em>True Predation</em></a><div class="score">7.0</div></div>
+<div class="slider-item"><a href="/reviews/6772/offending-age-of-perversion"><img src="album.cover?art=6772" alt="Offending Age of Perversion" /><br /><strong>Offending</strong><br /><em>Age of Perversion</em></a><div class="score">7.5</div></div>
+<div class="slider-item"><a href="/reviews/6804/king-of-asgard----to-north"><img src="album.cover?art=6804" alt="King Of Asgard ...to North" /><br /><strong>King Of Asgard</strong><br /><em>...to North</em></a><div class="score">7.5</div></div>
+</div>
+<div class="slider-row">
+<div class="slider-item"><a href="/reviews/6783/burning-love-rotten-thing-to-say"><img src="album.cover?art=6783" alt="Burning Love Rotten Thing to Say" /><br /><strong>Burning Love</strong><br /><em>Rotten Thing to Say</em></a><div class="score">7.0</div></div>
+<div class="slider-item"><a href="/reviews/6770/high-on-fire-the-art-of-self-defense-(reissue)"><img src="album.cover?art=6770" alt="High On Fire The Art Of Self Defense (Reissue)" /><br /><strong>High On Fire</strong><br /><em>The Art Of Self Defense (Reissue)</em></a><div class="score">7.5</div></div>
+<div class="slider-item"><a href="/reviews/6660/horseback-half-blood"><img src="album.cover?art=6660" alt="Horseback Half Blood" /><br /><strong>Horseback</strong><br /><em>Half Blood</em></a><div class="score">6.5</div></div>
+<div class="slider-item"><a href="/reviews/6732/aldebaran-embracing-the-lightless-depths"><img src="album.cover?art=6732" alt="Aldebaran Embracing the Lightless Depths" /><br /><strong>Aldebaran</strong><br /><em>Embracing the Lightless Depths</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6778/tank-war-nation"><img src="album.cover?art=6778" alt="Tank War Nation" /><br /><strong>Tank</strong><br /><em>War Nation</em></a><div class="score">6.5</div></div>
+<div class="slider-item"><a href="/reviews/6793/satanic-bloodspraying-at-the-mercy-of-satan"><img src="album.cover?art=6793" alt="Satanic Bloodspraying At the Mercy of Satan" /><br /><strong>Satanic Bloodspraying</strong><br /><em>At the Mercy of Satan</em></a><div class="score">8.5</div></div>
+<div class="slider-item"><a href="/reviews/6791/from-ashes-rise-rejoice-the-end---rage-of-sanity"><img src="album.cover?art=6791" alt="From Ashes Rise Rejoice The End / Rage Of Sanity" /><br /><strong>From Ashes Rise</strong><br /><em>Rejoice The End / Rage Of Sanity</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6743/ereb-altor-gastrike"><img src="album.cover?art=6743" alt="Ereb Altor Gastrike" /><br /><strong>Ereb Altor</strong><br /><em>Gastrike</em></a><div class="score">8.0</div></div>
+<div class="slider-item"><a href="/reviews/6794/catheter-southwest-doom-violence"><img src="album.cover?art=6794" alt="Catheter Southwest Doom Violence" /><br /><strong>Catheter</strong><br /><em>Southwest Doom Violence</em></a><div class="score">7.0</div></div>
+<div class="slider-item"><a href="/reviews/6759/power-theory-an-axe-to-grind"><img src="album.cover?art=6759" alt="Power Theory An Axe to Grind" /><br /><strong>Power Theory</strong><br /><em>An Axe to Grind</em></a><div class="score">6.0</div></div>
+</div>
+
+ </div>
+  
+
+ <script type="text/javascript">
+     $(document).ready(function () {
+         $('#latest-reviews-slider').cycle({
+             fx: 'scrollRight',
+             speed: 'fast',
+             timeout: 0,
+             next: '#slider-next-button',
+             prev: '#slider-back-button'
+         });
+     });
+</script>
+
+<div id="homepage-mid-horizontal-zone">
+    <script language="javascript" type="text/javascript" src="http://metalreview.com/bannermgr/abm.aspx?z=1"></script>
+</div>
+
+
+
+
+<div id="news-feed">
+<h2>News</h2><div class="news-feed-line"><a href="http://www.bravewords.com/news/190057" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> CENTURIAN To Release Contra Rationem Album This Winter</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190056" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> Southwest Terror Fest 2012 - Lineup Changes Announced</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190055" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> ROB ZOMBIE Premiers The Lords Of Salem At TIFF; Q&A Video Posted</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190054" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> THIN LIZZY Keyboardist Darren Wharton's DARE - Calm Before The Storm 2 Album Details Revealed</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190053" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> Japan's LIV MOON To Release Fourth Album; Features Past/Present Members Of EUROPE, ANGRA, HAMMERFALL</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190052" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> SLASH - Sydney Show To Premier This Friday, Free And In HD; Trailer Posted</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190051" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> KHAØS - New Band Featuring Members Of OUTLOUD, TRIBAL, JORN And ELIS To Release New EP In October; Teaser Posted </strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190050" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> RECKLESS LOVE Confirm Guests For London Residency Shows In October</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190049" target="_blank"><span class="news-link"><strong>NASHVILLE PUSSY Add Dates In France, Sweden To European Tour Schedule; Bassist Karen Cuda Sidelined With Back Injury	</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190048" target="_blank"><span class="news-link"><strong>CALIBAN Post Behind-The-Scenes Tour Footage</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190047" target="_blank"><span class="news-link"><strong>Ex-MERCYFUL FATE Drummer Kim Ruzz Forms New Band METALRUZZ</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+<div class="news-feed-line"><a href="http://www.bravewords.com/news/190046" target="_blank"><span class="news-link"><strong>GRAVE Mainman On Endless Procession Of Souls - "These Are The Most ‘Song-Oriented’ Tracks We’ve Done In A Long Time"</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
+
+</div>
+
+
+<div id="lashes-feed">
+<h2>Lashes</h2>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81760"><span class="new-lash">NEW</span> <span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">45 minutes ago by Chaosjunkie</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81759"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">1 hour ago by Harry Dick Rotten</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6746/resurgency-false-enlightenment#81758"><span class="lashes-link"><strong>Resurgency - False Enlightenment</strong></span></a><br /><span class="publish-date">3 hours ago by Anonymous</span></div>
+<div class="lashes-feed-line"><a href="/reviews/4095/witchcraft-the-alchemist#81757"><span class="lashes-link"><strong>Witchcraft - The Alchemist</strong></span></a><br /><span class="publish-date">5 hours ago by Luke_22</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81756"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">9 hours ago by chaosjunkie</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81755"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">10 hours ago by Compeller</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6827/manetheren-time#81754"><span class="lashes-link"><strong>Manetheren - Time</strong></span></a><br /><span class="publish-date">10 hours ago by xpmule</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6835/ufomammut-oro--opus-alter#81753"><span class="lashes-link"><strong>Ufomammut - Oro: Opus Alter</strong></span></a><br /><span class="publish-date">16 hours ago by Anonymous</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6835/ufomammut-oro--opus-alter#81752"><span class="lashes-link"><strong>Ufomammut - Oro: Opus Alter</strong></span></a><br /><span class="publish-date">17 hours ago by Harry Dick Rotten</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81751"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by Chaosjunkie</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81750"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by Anonymous</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81749"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by Anonymous</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81748"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by Anonymous</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81747"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by frantic</span></div>
+<div class="lashes-feed-line"><a href="/reviews/6829/blut-aus-nord-777---cosmosophy#81746"><span class="lashes-link"><strong>Blut Aus Nord - 777 - Cosmosophy</strong></span></a><br /><span class="publish-date">yesterday by Dimensional Bleedthrough</span></div>
+
+ </div>
+
+		</div>
+	</div>
+	<div id="ft">
+		
+
+<div id="template-footer">
+    <div class="left-column">
+        <ul>
+            <li><a href="/">Home</a></li>
+            <li><a href="/reviews/browse">Reviews</a></li>
+            <li><a href="/tags">Genre Tags</a></li>
+            <li><a href="http://community2.metalreview.com/blogs/editorials/default.aspx">Features</a></li>
+            <li><a href="/artists/browse">Artists</a></li>
+            <li><a href="/reviews/pipeline">Pipeline</a></li>
+            <li><a href="http://community2.metalreview.com/forums">Forums</a></li>
+            <li><a href="/aboutus">About Us</a></li>
+        </ul>
+    </div>
+    <div class="middle-column">
+        <ul>
+            <li><a href="/aboutus/disclaimer">Disclaimer</a></li>
+            <li><a href="/aboutus/privacypolicy">Privacy Policy</a></li>
+            <li><a href="/aboutus/advertising">Advertising</a></li>
+            <li><a href="http://community2.metalreview.com/blogs/eminor/archive/2008/10/27/write-for-metal-review.aspx">Write For Us</a></li>
+            <li><a href="/contactus">Contact Us</a></li>
+            <li><a href="/contactus">Digital Promos</a></li>
+            <li><a href="/contactus">Mailing Address</a></li>
+        </ul>
+    </div>
+    <div class="right-column">
+        <ul>
+            <li><a href="http://feeds.feedburner.com/metalreviews">Reviews RSS Feed</a></li>
+            <li><a href="http://twitter.com/metalreview">Twitter</a></li>
+            <li><a href="http://www.myspace.com/metalreviewdotcom">MySpace</a></li>
+            <li><a href="http://www.last.fm/group/MetalReview.com">Last.fm</a></li>
+            <li><a href="http://www.facebook.com/pages/MetalReviewcom/48371319443">Facebook</a></li>
+        </ul>
+    </div>
+    <div class="square-ad">
+        
+
+<!--JavaScript Tag // Tag for network 5110: Fixion Media // Website: Metalreview // Page: ROS // Placement: ROS-Middle-300 x 250 (1127996) // created at: Oct 19, 2009 6:48:27 PM-->
+<script type="text/javascript" language="javascript"><!--
+    document.write('<scr' + 'ipt language="javascript1.1" src="http://adserver.adtechus.com/addyn/3.0/5110/1127996/0/170/ADTECH;loc=100;target=_blank;key=key1+key2+key3+key4;grp=[group];misc=' + new Date().getTime() + '"></scri' + 'pt>');
+//-->
+</script><noscript><a href="http://adserver.adtechus.com/adlink/3.0/5110/1127996/0/170/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" target="_blank"><img src="http://adserver.adtechus.com/adserv/3.0/5110/1127996/0/170/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" border="0" width="300" height="250"></a></noscript>
+<!-- End of JavaScript Tag -->
+    </div>
+</div>
+	</div>
+</div>
+
+    <script type="text/javascript">
+        var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+        document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+    </script>
+
+    <script type="text/javascript">
+        var pageTracker = _gat._getTracker("UA-3455310-1");
+        pageTracker._initData();
+        pageTracker._trackPageview();
+    </script>
+
+    <!--JavaScript Tag // Tag for network 5110: Fixion Media // Website: Metalreview // Page: BACKGROUND ADS // Placement: BACKGROUND ADS-Top-1 x 1 (2186116) // created at: Aug 18, 2011 7:20:38 PM-->
+    <script language="javascript"><!--
+        document.write('<scr' + 'ipt language="javascript1.1" src="http://adserver.adtechus.com/addyn/3.0/5110/2186116/0/16/ADTECH;loc=100;target=_blank;key=key1+key2+key3+key4;grp=[group];misc=' + new Date().getTime() + '"></scri' + 'pt>');
+    //-->
+    </script><noscript><a href="http://adserver.adtechus.com/adlink/3.0/5110/2186116/0/16/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" target="_blank"><img src="http://adserver.adtechus.com/adserv/3.0/5110/2186116/0/16/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" border="0" width="1" height="1"></a></noscript>
+    <!-- End of JavaScript Tag -->
+    
+</body>
+</html>

+ 102 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/testdata/page.html

@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html lang="en" ng-app="app">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+        <title>
+            Provok.in
+        </title>
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <meta name="description" content="Provok.in - Prove your point. State an affirmation, back it up with evidence, unveil the truth.">
+        <meta name="author" content="Martin Angers">
+        <link href="http://fonts.googleapis.com/css?family=Belgrano" rel="stylesheet" type="text/css">
+        <!--[if lt IE 9]><link href="http://fonts.googleapis.com/css?family=Belgrano" rel="stylesheet" type="text/css"><link href="http://fonts.googleapis.com/css?family=Belgrano:400italic" rel="stylesheet" type="text/css"><link href="http://fonts.googleapis.com/css?family=Belgrano:700" rel="stylesheet" type="text/css"><link href="http://fonts.googleapis.com/css?family=Belgrano:700italic" rel="stylesheet" type="text/css"><![endif]-->
+        <link href="/css/pvk.min.css" rel="stylesheet" type="text/css">
+    </head>
+    <body>
+        <div class="container-fluid" id="cf1">
+            <div class="row-fluid">
+                <div class="pvk-gutter">
+                    &nbsp;
+                </div>
+                <div class="pvk-content" id="pc1">
+                    <div ng-controller="HeroCtrl" class="hero-unit">
+                        <div class="container-fluid" id="cf2">
+                            <div class="row-fluid" id="cf2-1">
+                                <div class="span12">
+                                    <h1>
+                                        <a href="/">Provok<span class="green">.</span><span class="red">i</span>n</a>
+                                    </h1>
+                                    <p>
+                                        Prove your point.
+                                    </p>
+                                </div>
+                            </div>
+                            <div class="row-fluid" id="cf2-2">
+                                <div class="span12 alert alert-error">
+                                    <strong>Beta Version.</strong> Things may change. Or disappear. Or fail miserably. If it's the latter, <a href="https://github.com/PuerkitoBio/Provok.in-issues" target="_blank" class="link">please file an issue.</a>
+                                </div>
+                            </div>
+                            <div ng-cloak="" ng-show="isLoggedOut() &amp;&amp; !hideLogin" class="row-fluid" id="cf2-3">
+                                <a ng-href="{{ROUTES.login}}" class="btn btn-primary">Sign in. Painless.</a> <span>or</span> <a ng-href="{{ROUTES.help}}" class="link">learn more about provok.in.</a>
+                            </div>
+                            <div ng-cloak="" ng-show="isLoggedIn()" class="row-fluid logged-in-state" id="cf2-4">
+                                <span>Welcome,</span> <a ng-href="{{ROUTES.profile}}" class="link">{{getUserName()}}</a> <span>(</span> <a ng-click="doLogout($event)" class="link">logout</a> <span>)</span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="pvk-gutter">
+                    &nbsp;
+                </div>
+            </div>
+            <div class="row-fluid">
+                <div class="pvk-gutter">
+                    &nbsp;
+                </div>
+                <div class="pvk-content" id="pc2">
+                    <div class="container-fluid" id="cf3">
+                        <div class="row-fluid">
+                            <div ng-cloak="" view-on-display="" ng-controller="MsgCtrl" ng-class="{'displayed': blockIsDisplayed}" class="message-box">
+                                <div ng-class="{'alert-info': isInfo, 'alert-error': !isInfo, 'displayed': isDisplayed}" class="alert">
+                                    <a ng-click="hideMessage(true, $event)" class="close">×</a>
+                                    <h4 class="alert-heading">
+                                        {{ title }}
+                                    </h4>
+                                    <p>
+                                        {{ message }}
+                                    </p>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="container-fluid" id="cf4">
+                        <div ng-controller="ShareCtrl" ng-hide="isHidden" class="row-fluid center-content"></div>
+                    </div>
+                    <div ng-view=""></div>
+                </div>
+                <div class="pvk-gutter">
+                    &nbsp;
+                </div>
+            </div>
+            <div class="row-fluid">
+                <div class="pvk-gutter">
+                    &nbsp;
+                </div>
+                <div class="pvk-content">
+                    <div class="footer">
+                        <p>
+                            <a href="/" class="link">Home</a> <span>|</span> <a href="/about" class="link">About</a> <span>|</span> <a href="/help" class="link">Help</a>
+                        </p>
+                        <p>
+                            <small>© 2012 Martin Angers</small>
+                        </p>
+                    </div>
+                </div>
+                <div class="pvk-gutter">
+                    &nbsp;
+                </div>
+            </div>
+        </div>
+    </body>
+</html>

+ 24 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/testdata/page2.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests for siblings</title>
+  </head>
+  <BODY>
+    <div id="main">
+      <div id="n1" class="one even row"></div>
+      <div id="n2" class="two odd row"></div>
+      <div id="n3" class="three even row"></div>
+      <div id="n4" class="four odd row"></div>
+      <div id="n5" class="five even row"></div>
+      <div id="n6" class="six odd row"></div>
+    </div>
+    <div id="foot">
+      <div id="nf1" class="one even row"></div>
+      <div id="nf2" class="two odd row"></div>
+      <div id="nf3" class="three even row"></div>
+      <div id="nf4" class="four odd row"></div>
+      <div id="nf5" class="five even row odder"></div>
+      <div id="nf6" class="six odd row"></div>
+    </div>
+  </BODY>
+</html>

+ 24 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/testdata/page3.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests for siblings</title>
+  </head>
+  <BODY>
+    <div id="main">
+      <div id="n1" class="one even row">hello</div>
+      <div id="n2" class="two odd row"></div>
+      <div id="n3" class="three even row"></div>
+      <div id="n4" class="four odd row"></div>
+      <div id="n5" class="five even row"></div>
+      <div id="n6" class="six odd row"></div>
+    </div>
+    <div id="foot">
+      <div id="nf1" class="one even row">text</div>
+      <div id="nf2" class="two odd row"></div>
+      <div id="nf3" class="three even row"></div>
+      <div id="nf4" class="four odd row"></div>
+      <div id="nf5" class="five even row odder"></div>
+      <div id="nf6" class="six odd row"></div>
+    </div>
+  </BODY>
+</html>

+ 698 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/traversal.go

@@ -0,0 +1,698 @@
+package goquery
+
+import "golang.org/x/net/html"
+
+type siblingType int
+
+// Sibling type, used internally when iterating over children at the same
+// level (siblings) to specify which nodes are requested.
+const (
+	siblingPrevUntil siblingType = iota - 3
+	siblingPrevAll
+	siblingPrev
+	siblingAll
+	siblingNext
+	siblingNextAll
+	siblingNextUntil
+	siblingAllIncludingNonElements
+)
+
+// Find gets the descendants of each element in the current set of matched
+// elements, filtered by a selector. It returns a new Selection object
+// containing these matched elements.
+func (s *Selection) Find(selector string) *Selection {
+	return pushStack(s, findWithMatcher(s.Nodes, compileMatcher(selector)))
+}
+
+// FindMatcher gets the descendants of each element in the current set of matched
+// elements, filtered by the matcher. It returns a new Selection object
+// containing these matched elements.
+func (s *Selection) FindMatcher(m Matcher) *Selection {
+	return pushStack(s, findWithMatcher(s.Nodes, m))
+}
+
+// FindSelection gets the descendants of each element in the current
+// Selection, filtered by a Selection. It returns a new Selection object
+// containing these matched elements.
+func (s *Selection) FindSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return pushStack(s, nil)
+	}
+	return s.FindNodes(sel.Nodes...)
+}
+
+// FindNodes gets the descendants of each element in the current
+// Selection, filtered by some nodes. It returns a new Selection object
+// containing these matched elements.
+func (s *Selection) FindNodes(nodes ...*html.Node) *Selection {
+	return pushStack(s, mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
+		if sliceContains(s.Nodes, n) {
+			return []*html.Node{n}
+		}
+		return nil
+	}))
+}
+
+// Contents gets the children of each element in the Selection,
+// including text and comment nodes. It returns a new Selection object
+// containing these elements.
+func (s *Selection) Contents() *Selection {
+	return pushStack(s, getChildrenNodes(s.Nodes, siblingAllIncludingNonElements))
+}
+
+// ContentsFiltered gets the children of each element in the Selection,
+// filtered by the specified selector. It returns a new Selection
+// object containing these elements. Since selectors only act on Element nodes,
+// this function is an alias to ChildrenFiltered unless the selector is empty,
+// in which case it is an alias to Contents.
+func (s *Selection) ContentsFiltered(selector string) *Selection {
+	if selector != "" {
+		return s.ChildrenFiltered(selector)
+	}
+	return s.Contents()
+}
+
+// ContentsMatcher gets the children of each element in the Selection,
+// filtered by the specified matcher. It returns a new Selection
+// object containing these elements. Since matchers only act on Element nodes,
+// this function is an alias to ChildrenMatcher.
+func (s *Selection) ContentsMatcher(m Matcher) *Selection {
+	return s.ChildrenMatcher(m)
+}
+
+// Children gets the child elements of each element in the Selection.
+// It returns a new Selection object containing these elements.
+func (s *Selection) Children() *Selection {
+	return pushStack(s, getChildrenNodes(s.Nodes, siblingAll))
+}
+
+// ChildrenFiltered gets the child elements of each element in the Selection,
+// filtered by the specified selector. It returns a new
+// Selection object containing these elements.
+func (s *Selection) ChildrenFiltered(selector string) *Selection {
+	return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), compileMatcher(selector))
+}
+
+// ChildrenMatcher gets the child elements of each element in the Selection,
+// filtered by the specified matcher. It returns a new
+// Selection object containing these elements.
+func (s *Selection) ChildrenMatcher(m Matcher) *Selection {
+	return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), m)
+}
+
+// Parent gets the parent of each element in the Selection. It returns a
+// new Selection object containing the matched elements.
+func (s *Selection) Parent() *Selection {
+	return pushStack(s, getParentNodes(s.Nodes))
+}
+
+// ParentFiltered gets the parent of each element in the Selection filtered by a
+// selector. It returns a new Selection object containing the matched elements.
+func (s *Selection) ParentFiltered(selector string) *Selection {
+	return filterAndPush(s, getParentNodes(s.Nodes), compileMatcher(selector))
+}
+
+// ParentMatcher gets the parent of each element in the Selection filtered by a
+// matcher. It returns a new Selection object containing the matched elements.
+func (s *Selection) ParentMatcher(m Matcher) *Selection {
+	return filterAndPush(s, getParentNodes(s.Nodes), m)
+}
+
+// Closest gets the first element that matches the selector by testing the
+// element itself and traversing up through its ancestors in the DOM tree.
+func (s *Selection) Closest(selector string) *Selection {
+	cs := compileMatcher(selector)
+	return s.ClosestMatcher(cs)
+}
+
+// ClosestMatcher gets the first element that matches the matcher by testing the
+// element itself and traversing up through its ancestors in the DOM tree.
+func (s *Selection) ClosestMatcher(m Matcher) *Selection {
+	return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
+		// For each node in the selection, test the node itself, then each parent
+		// until a match is found.
+		for ; n != nil; n = n.Parent {
+			if m.Match(n) {
+				return []*html.Node{n}
+			}
+		}
+		return nil
+	}))
+}
+
+// ClosestNodes gets the first element that matches one of the nodes by testing the
+// element itself and traversing up through its ancestors in the DOM tree.
+func (s *Selection) ClosestNodes(nodes ...*html.Node) *Selection {
+	set := make(map[*html.Node]bool)
+	for _, n := range nodes {
+		set[n] = true
+	}
+	return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
+		// For each node in the selection, test the node itself, then each parent
+		// until a match is found.
+		for ; n != nil; n = n.Parent {
+			if set[n] {
+				return []*html.Node{n}
+			}
+		}
+		return nil
+	}))
+}
+
+// ClosestSelection gets the first element that matches one of the nodes in the
+// Selection by testing the element itself and traversing up through its ancestors
+// in the DOM tree.
+func (s *Selection) ClosestSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return pushStack(s, nil)
+	}
+	return s.ClosestNodes(sel.Nodes...)
+}
+
+// Parents gets the ancestors of each element in the current Selection. It
+// returns a new Selection object with the matched elements.
+func (s *Selection) Parents() *Selection {
+	return pushStack(s, getParentsNodes(s.Nodes, nil, nil))
+}
+
+// ParentsFiltered gets the ancestors of each element in the current
+// Selection. It returns a new Selection object with the matched elements.
+func (s *Selection) ParentsFiltered(selector string) *Selection {
+	return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), compileMatcher(selector))
+}
+
+// ParentsMatcher gets the ancestors of each element in the current
+// Selection. It returns a new Selection object with the matched elements.
+func (s *Selection) ParentsMatcher(m Matcher) *Selection {
+	return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), m)
+}
+
+// ParentsUntil gets the ancestors of each element in the Selection, up to but
+// not including the element matched by the selector. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) ParentsUntil(selector string) *Selection {
+	return pushStack(s, getParentsNodes(s.Nodes, compileMatcher(selector), nil))
+}
+
+// ParentsUntilMatcher gets the ancestors of each element in the Selection, up to but
+// not including the element matched by the matcher. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) ParentsUntilMatcher(m Matcher) *Selection {
+	return pushStack(s, getParentsNodes(s.Nodes, m, nil))
+}
+
+// ParentsUntilSelection gets the ancestors of each element in the Selection,
+// up to but not including the elements in the specified Selection. It returns a
+// new Selection object containing the matched elements.
+func (s *Selection) ParentsUntilSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return s.Parents()
+	}
+	return s.ParentsUntilNodes(sel.Nodes...)
+}
+
+// ParentsUntilNodes gets the ancestors of each element in the Selection,
+// up to but not including the specified nodes. It returns a
+// new Selection object containing the matched elements.
+func (s *Selection) ParentsUntilNodes(nodes ...*html.Node) *Selection {
+	return pushStack(s, getParentsNodes(s.Nodes, nil, nodes))
+}
+
+// ParentsFilteredUntil is like ParentsUntil, with the option to filter the
+// results based on a selector string. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) ParentsFilteredUntil(filterSelector, untilSelector string) *Selection {
+	return filterAndPush(s, getParentsNodes(s.Nodes, compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
+}
+
+// ParentsFilteredUntilMatcher is like ParentsUntilMatcher, with the option to filter the
+// results based on a matcher. It returns a new Selection object containing the matched elements.
+func (s *Selection) ParentsFilteredUntilMatcher(filter, until Matcher) *Selection {
+	return filterAndPush(s, getParentsNodes(s.Nodes, until, nil), filter)
+}
+
+// ParentsFilteredUntilSelection is like ParentsUntilSelection, with the
+// option to filter the results based on a selector string. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) ParentsFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
+	return s.ParentsMatcherUntilSelection(compileMatcher(filterSelector), sel)
+}
+
+// ParentsMatcherUntilSelection is like ParentsUntilSelection, with the
+// option to filter the results based on a matcher. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) ParentsMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
+	if sel == nil {
+		return s.ParentsMatcher(filter)
+	}
+	return s.ParentsMatcherUntilNodes(filter, sel.Nodes...)
+}
+
+// ParentsFilteredUntilNodes is like ParentsUntilNodes, with the
+// option to filter the results based on a selector string. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) ParentsFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
+	return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), compileMatcher(filterSelector))
+}
+
+// ParentsMatcherUntilNodes is like ParentsUntilNodes, with the
+// option to filter the results based on a matcher. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) ParentsMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
+	return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), filter)
+}
+
+// Siblings gets the siblings of each element in the Selection. It returns
+// a new Selection object containing the matched elements.
+func (s *Selection) Siblings() *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil))
+}
+
+// SiblingsFiltered gets the siblings of each element in the Selection
+// filtered by a selector. It returns a new Selection object containing the
+// matched elements.
+func (s *Selection) SiblingsFiltered(selector string) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), compileMatcher(selector))
+}
+
+// SiblingsMatcher gets the siblings of each element in the Selection
+// filtered by a matcher. It returns a new Selection object containing the
+// matched elements.
+func (s *Selection) SiblingsMatcher(m Matcher) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), m)
+}
+
+// Next gets the immediately following sibling of each element in the
+// Selection. It returns a new Selection object containing the matched elements.
+func (s *Selection) Next() *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil))
+}
+
+// NextFiltered gets the immediately following sibling of each element in the
+// Selection filtered by a selector. It returns a new Selection object
+// containing the matched elements.
+func (s *Selection) NextFiltered(selector string) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), compileMatcher(selector))
+}
+
+// NextMatcher gets the immediately following sibling of each element in the
+// Selection filtered by a matcher. It returns a new Selection object
+// containing the matched elements.
+func (s *Selection) NextMatcher(m Matcher) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), m)
+}
+
+// NextAll gets all the following siblings of each element in the
+// Selection. It returns a new Selection object containing the matched elements.
+func (s *Selection) NextAll() *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil))
+}
+
+// NextAllFiltered gets all the following siblings of each element in the
+// Selection filtered by a selector. It returns a new Selection object
+// containing the matched elements.
+func (s *Selection) NextAllFiltered(selector string) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), compileMatcher(selector))
+}
+
+// NextAllMatcher gets all the following siblings of each element in the
+// Selection filtered by a matcher. It returns a new Selection object
+// containing the matched elements.
+func (s *Selection) NextAllMatcher(m Matcher) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), m)
+}
+
+// Prev gets the immediately preceding sibling of each element in the
+// Selection. It returns a new Selection object containing the matched elements.
+func (s *Selection) Prev() *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil))
+}
+
+// PrevFiltered gets the immediately preceding sibling of each element in the
+// Selection filtered by a selector. It returns a new Selection object
+// containing the matched elements.
+func (s *Selection) PrevFiltered(selector string) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), compileMatcher(selector))
+}
+
+// PrevMatcher gets the immediately preceding sibling of each element in the
+// Selection filtered by a matcher. It returns a new Selection object
+// containing the matched elements.
+func (s *Selection) PrevMatcher(m Matcher) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), m)
+}
+
+// PrevAll gets all the preceding siblings of each element in the
+// Selection. It returns a new Selection object containing the matched elements.
+func (s *Selection) PrevAll() *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil))
+}
+
+// PrevAllFiltered gets all the preceding siblings of each element in the
+// Selection filtered by a selector. It returns a new Selection object
+// containing the matched elements.
+func (s *Selection) PrevAllFiltered(selector string) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), compileMatcher(selector))
+}
+
+// PrevAllMatcher gets all the preceding siblings of each element in the
+// Selection filtered by a matcher. It returns a new Selection object
+// containing the matched elements.
+func (s *Selection) PrevAllMatcher(m Matcher) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), m)
+}
+
+// NextUntil gets all following siblings of each element up to but not
+// including the element matched by the selector. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) NextUntil(selector string) *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
+		compileMatcher(selector), nil))
+}
+
+// NextUntilMatcher gets all following siblings of each element up to but not
+// including the element matched by the matcher. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) NextUntilMatcher(m Matcher) *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
+		m, nil))
+}
+
+// NextUntilSelection gets all following siblings of each element up to but not
+// including the element matched by the Selection. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) NextUntilSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return s.NextAll()
+	}
+	return s.NextUntilNodes(sel.Nodes...)
+}
+
+// NextUntilNodes gets all following siblings of each element up to but not
+// including the element matched by the nodes. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) NextUntilNodes(nodes ...*html.Node) *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
+		nil, nodes))
+}
+
+// PrevUntil gets all preceding siblings of each element up to but not
+// including the element matched by the selector. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) PrevUntil(selector string) *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
+		compileMatcher(selector), nil))
+}
+
+// PrevUntilMatcher gets all preceding siblings of each element up to but not
+// including the element matched by the matcher. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) PrevUntilMatcher(m Matcher) *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
+		m, nil))
+}
+
+// PrevUntilSelection gets all preceding siblings of each element up to but not
+// including the element matched by the Selection. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) PrevUntilSelection(sel *Selection) *Selection {
+	if sel == nil {
+		return s.PrevAll()
+	}
+	return s.PrevUntilNodes(sel.Nodes...)
+}
+
+// PrevUntilNodes gets all preceding siblings of each element up to but not
+// including the element matched by the nodes. It returns a new Selection
+// object containing the matched elements.
+func (s *Selection) PrevUntilNodes(nodes ...*html.Node) *Selection {
+	return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
+		nil, nodes))
+}
+
+// NextFilteredUntil is like NextUntil, with the option to filter
+// the results based on a selector string.
+// It returns a new Selection object containing the matched elements.
+func (s *Selection) NextFilteredUntil(filterSelector, untilSelector string) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
+		compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
+}
+
+// NextFilteredUntilMatcher is like NextUntilMatcher, with the option to filter
+// the results based on a matcher.
+// It returns a new Selection object containing the matched elements.
+func (s *Selection) NextFilteredUntilMatcher(filter, until Matcher) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
+		until, nil), filter)
+}
+
+// NextFilteredUntilSelection is like NextUntilSelection, with the
+// option to filter the results based on a selector string. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) NextFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
+	return s.NextMatcherUntilSelection(compileMatcher(filterSelector), sel)
+}
+
+// NextMatcherUntilSelection is like NextUntilSelection, with the
+// option to filter the results based on a matcher. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) NextMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
+	if sel == nil {
+		return s.NextMatcher(filter)
+	}
+	return s.NextMatcherUntilNodes(filter, sel.Nodes...)
+}
+
+// NextFilteredUntilNodes is like NextUntilNodes, with the
+// option to filter the results based on a selector string. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) NextFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
+		nil, nodes), compileMatcher(filterSelector))
+}
+
+// NextMatcherUntilNodes is like NextUntilNodes, with the
+// option to filter the results based on a matcher. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) NextMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
+		nil, nodes), filter)
+}
+
+// PrevFilteredUntil is like PrevUntil, with the option to filter
+// the results based on a selector string.
+// It returns a new Selection object containing the matched elements.
+func (s *Selection) PrevFilteredUntil(filterSelector, untilSelector string) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
+		compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
+}
+
+// PrevFilteredUntilMatcher is like PrevUntilMatcher, with the option to filter
+// the results based on a matcher.
+// It returns a new Selection object containing the matched elements.
+func (s *Selection) PrevFilteredUntilMatcher(filter, until Matcher) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
+		until, nil), filter)
+}
+
+// PrevFilteredUntilSelection is like PrevUntilSelection, with the
+// option to filter the results based on a selector string. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) PrevFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
+	return s.PrevMatcherUntilSelection(compileMatcher(filterSelector), sel)
+}
+
+// PrevMatcherUntilSelection is like PrevUntilSelection, with the
+// option to filter the results based on a matcher. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) PrevMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
+	if sel == nil {
+		return s.PrevMatcher(filter)
+	}
+	return s.PrevMatcherUntilNodes(filter, sel.Nodes...)
+}
+
+// PrevFilteredUntilNodes is like PrevUntilNodes, with the
+// option to filter the results based on a selector string. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) PrevFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
+		nil, nodes), compileMatcher(filterSelector))
+}
+
+// PrevMatcherUntilNodes is like PrevUntilNodes, with the
+// option to filter the results based on a matcher. It returns a new
+// Selection object containing the matched elements.
+func (s *Selection) PrevMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
+	return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
+		nil, nodes), filter)
+}
+
+// Filter and push filters the nodes based on a matcher, and pushes the results
+// on the stack, with the srcSel as previous selection.
+func filterAndPush(srcSel *Selection, nodes []*html.Node, m Matcher) *Selection {
+	// Create a temporary Selection with the specified nodes to filter using winnow
+	sel := &Selection{nodes, srcSel.document, nil}
+	// Filter based on matcher and push on stack
+	return pushStack(srcSel, winnow(sel, m, true))
+}
+
+// Internal implementation of Find that return raw nodes.
+func findWithMatcher(nodes []*html.Node, m Matcher) []*html.Node {
+	// Map nodes to find the matches within the children of each node
+	return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
+		// Go down one level, becausejQuery's Find selects only within descendants
+		for c := n.FirstChild; c != nil; c = c.NextSibling {
+			if c.Type == html.ElementNode {
+				result = append(result, m.MatchAll(c)...)
+			}
+		}
+		return
+	})
+}
+
+// Internal implementation to get all parent nodes, stopping at the specified
+// node (or nil if no stop).
+func getParentsNodes(nodes []*html.Node, stopm Matcher, stopNodes []*html.Node) []*html.Node {
+	return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
+		for p := n.Parent; p != nil; p = p.Parent {
+			sel := newSingleSelection(p, nil)
+			if stopm != nil {
+				if sel.IsMatcher(stopm) {
+					break
+				}
+			} else if len(stopNodes) > 0 {
+				if sel.IsNodes(stopNodes...) {
+					break
+				}
+			}
+			if p.Type == html.ElementNode {
+				result = append(result, p)
+			}
+		}
+		return
+	})
+}
+
+// Internal implementation of sibling nodes that return a raw slice of matches.
+func getSiblingNodes(nodes []*html.Node, st siblingType, untilm Matcher, untilNodes []*html.Node) []*html.Node {
+	var f func(*html.Node) bool
+
+	// If the requested siblings are ...Until, create the test function to
+	// determine if the until condition is reached (returns true if it is)
+	if st == siblingNextUntil || st == siblingPrevUntil {
+		f = func(n *html.Node) bool {
+			if untilm != nil {
+				// Matcher-based condition
+				sel := newSingleSelection(n, nil)
+				return sel.IsMatcher(untilm)
+			} else if len(untilNodes) > 0 {
+				// Nodes-based condition
+				sel := newSingleSelection(n, nil)
+				return sel.IsNodes(untilNodes...)
+			}
+			return false
+		}
+	}
+
+	return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
+		return getChildrenWithSiblingType(n.Parent, st, n, f)
+	})
+}
+
+// Gets the children nodes of each node in the specified slice of nodes,
+// based on the sibling type request.
+func getChildrenNodes(nodes []*html.Node, st siblingType) []*html.Node {
+	return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
+		return getChildrenWithSiblingType(n, st, nil, nil)
+	})
+}
+
+// Gets the children of the specified parent, based on the requested sibling
+// type, skipping a specified node if required.
+func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *html.Node,
+	untilFunc func(*html.Node) bool) (result []*html.Node) {
+
+	// Create the iterator function
+	var iter = func(cur *html.Node) (ret *html.Node) {
+		// Based on the sibling type requested, iterate the right way
+		for {
+			switch st {
+			case siblingAll, siblingAllIncludingNonElements:
+				if cur == nil {
+					// First iteration, start with first child of parent
+					// Skip node if required
+					if ret = parent.FirstChild; ret == skipNode && skipNode != nil {
+						ret = skipNode.NextSibling
+					}
+				} else {
+					// Skip node if required
+					if ret = cur.NextSibling; ret == skipNode && skipNode != nil {
+						ret = skipNode.NextSibling
+					}
+				}
+			case siblingPrev, siblingPrevAll, siblingPrevUntil:
+				if cur == nil {
+					// Start with previous sibling of the skip node
+					ret = skipNode.PrevSibling
+				} else {
+					ret = cur.PrevSibling
+				}
+			case siblingNext, siblingNextAll, siblingNextUntil:
+				if cur == nil {
+					// Start with next sibling of the skip node
+					ret = skipNode.NextSibling
+				} else {
+					ret = cur.NextSibling
+				}
+			default:
+				panic("Invalid sibling type.")
+			}
+			if ret == nil || ret.Type == html.ElementNode || st == siblingAllIncludingNonElements {
+				return
+			}
+			// Not a valid node, try again from this one
+			cur = ret
+		}
+	}
+
+	for c := iter(nil); c != nil; c = iter(c) {
+		// If this is an ...Until case, test before append (returns true
+		// if the until condition is reached)
+		if st == siblingNextUntil || st == siblingPrevUntil {
+			if untilFunc(c) {
+				return
+			}
+		}
+		result = append(result, c)
+		if st == siblingNext || st == siblingPrev {
+			// Only one node was requested (immediate next or previous), so exit
+			return
+		}
+	}
+	return
+}
+
+// Internal implementation of parent nodes that return a raw slice of Nodes.
+func getParentNodes(nodes []*html.Node) []*html.Node {
+	return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
+		if n.Parent != nil && n.Parent.Type == html.ElementNode {
+			return []*html.Node{n.Parent}
+		}
+		return nil
+	})
+}
+
+// Internal map function used by many traversing methods. Takes the source nodes
+// to iterate on and the mapping function that returns an array of nodes.
+// Returns an array of nodes mapped by calling the callback function once for
+// each node in the source nodes.
+func mapNodes(nodes []*html.Node, f func(int, *html.Node) []*html.Node) (result []*html.Node) {
+	set := make(map[*html.Node]bool)
+	for i, n := range nodes {
+		if vals := f(i, n); len(vals) > 0 {
+			result = appendWithoutDuplicates(result, vals, set)
+		}
+	}
+	return result
+}

+ 793 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/traversal_test.go

@@ -0,0 +1,793 @@
+package goquery
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestFind(t *testing.T) {
+	sel := Doc().Find("div.row-fluid")
+	assertLength(t, sel.Nodes, 9)
+}
+
+func TestFindRollback(t *testing.T) {
+	sel := Doc().Find("div.row-fluid")
+	sel2 := sel.Find("a").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestFindNotSelf(t *testing.T) {
+	sel := Doc().Find("h1").Find("h1")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestFindInvalid(t *testing.T) {
+	sel := Doc().Find(":+ ^")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestFindBig(t *testing.T) {
+	doc := DocW()
+	sel := doc.Find("li")
+	assertLength(t, sel.Nodes, 373)
+	sel2 := doc.Find("span")
+	assertLength(t, sel2.Nodes, 448)
+	sel3 := sel.FindSelection(sel2)
+	assertLength(t, sel3.Nodes, 248)
+}
+
+func TestChainedFind(t *testing.T) {
+	sel := Doc().Find("div.hero-unit").Find(".row-fluid")
+	assertLength(t, sel.Nodes, 4)
+}
+
+func TestChainedFindInvalid(t *testing.T) {
+	sel := Doc().Find("div.hero-unit").Find("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestChildren(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Children()
+	assertLength(t, sel.Nodes, 5)
+}
+
+func TestChildrenRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Children().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestContents(t *testing.T) {
+	sel := Doc().Find(".pvk-content").Contents()
+	assertLength(t, sel.Nodes, 13)
+}
+
+func TestContentsRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.Contents().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestChildrenFiltered(t *testing.T) {
+	sel := Doc().Find(".pvk-content").ChildrenFiltered(".hero-unit")
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestChildrenFilteredInvalid(t *testing.T) {
+	sel := Doc().Find(".pvk-content").ChildrenFiltered("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestChildrenFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.ChildrenFiltered(".hero-unit").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestContentsFiltered(t *testing.T) {
+	sel := Doc().Find(".pvk-content").ContentsFiltered(".hero-unit")
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestContentsFilteredInvalid(t *testing.T) {
+	sel := Doc().Find(".pvk-content").ContentsFiltered("~")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestContentsFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-content")
+	sel2 := sel.ContentsFiltered(".hero-unit").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestChildrenFilteredNone(t *testing.T) {
+	sel := Doc().Find(".pvk-content").ChildrenFiltered("a.btn")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestParent(t *testing.T) {
+	sel := Doc().Find(".container-fluid").Parent()
+	assertLength(t, sel.Nodes, 3)
+}
+
+func TestParentRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.Parent().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParentBody(t *testing.T) {
+	sel := Doc().Find("body").Parent()
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestParentFiltered(t *testing.T) {
+	sel := Doc().Find(".container-fluid").ParentFiltered(".hero-unit")
+	assertLength(t, sel.Nodes, 1)
+	assertClass(t, sel, "hero-unit")
+}
+
+func TestParentFilteredInvalid(t *testing.T) {
+	sel := Doc().Find(".container-fluid").ParentFiltered("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestParentFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.ParentFiltered(".hero-unit").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParents(t *testing.T) {
+	sel := Doc().Find(".container-fluid").Parents()
+	assertLength(t, sel.Nodes, 8)
+}
+
+func TestParentsOrder(t *testing.T) {
+	sel := Doc().Find("#cf2").Parents()
+	assertLength(t, sel.Nodes, 6)
+	assertSelectionIs(t, sel, ".hero-unit", ".pvk-content", "div.row-fluid", "#cf1", "body", "html")
+}
+
+func TestParentsRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.Parents().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParentsFiltered(t *testing.T) {
+	sel := Doc().Find(".container-fluid").ParentsFiltered("body")
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestParentsFilteredInvalid(t *testing.T) {
+	sel := Doc().Find(".container-fluid").ParentsFiltered("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestParentsFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.ParentsFiltered("body").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParentsUntil(t *testing.T) {
+	sel := Doc().Find(".container-fluid").ParentsUntil("body")
+	assertLength(t, sel.Nodes, 6)
+}
+
+func TestParentsUntilInvalid(t *testing.T) {
+	sel := Doc().Find(".container-fluid").ParentsUntil("")
+	assertLength(t, sel.Nodes, 8)
+}
+
+func TestParentsUntilRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.ParentsUntil("body").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParentsUntilSelection(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".pvk-content")
+	sel = sel.ParentsUntilSelection(sel2)
+	assertLength(t, sel.Nodes, 3)
+}
+
+func TestParentsUntilSelectionRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".pvk-content")
+	sel2 = sel.ParentsUntilSelection(sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParentsUntilNodes(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".pvk-content, .hero-unit")
+	sel = sel.ParentsUntilNodes(sel2.Nodes...)
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestParentsUntilNodesRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".pvk-content, .hero-unit")
+	sel2 = sel.ParentsUntilNodes(sel2.Nodes...).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParentsFilteredUntil(t *testing.T) {
+	sel := Doc().Find(".container-fluid").ParentsFilteredUntil(".pvk-content", "body")
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestParentsFilteredUntilInvalid(t *testing.T) {
+	sel := Doc().Find(".container-fluid").ParentsFilteredUntil("", "")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestParentsFilteredUntilRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.ParentsFilteredUntil(".pvk-content", "body").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParentsFilteredUntilSelection(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".row-fluid")
+	sel = sel.ParentsFilteredUntilSelection("div", sel2)
+	assertLength(t, sel.Nodes, 3)
+}
+
+func TestParentsFilteredUntilSelectionRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".row-fluid")
+	sel2 = sel.ParentsFilteredUntilSelection("div", sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestParentsFilteredUntilNodes(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".row-fluid")
+	sel = sel.ParentsFilteredUntilNodes("body", sel2.Nodes...)
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestParentsFilteredUntilNodesRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := Doc().Find(".row-fluid")
+	sel2 = sel.ParentsFilteredUntilNodes("body", sel2.Nodes...).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestSiblings(t *testing.T) {
+	sel := Doc().Find("h1").Siblings()
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestSiblingsRollback(t *testing.T) {
+	sel := Doc().Find("h1")
+	sel2 := sel.Siblings().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestSiblings2(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter").Siblings()
+	assertLength(t, sel.Nodes, 9)
+}
+
+func TestSiblings3(t *testing.T) {
+	sel := Doc().Find("body>.container-fluid").Siblings()
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestSiblingsFiltered(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter").SiblingsFiltered(".pvk-content")
+	assertLength(t, sel.Nodes, 3)
+}
+
+func TestSiblingsFilteredInvalid(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter").SiblingsFiltered("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestSiblingsFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter")
+	sel2 := sel.SiblingsFiltered(".pvk-content").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNext(t *testing.T) {
+	sel := Doc().Find("h1").Next()
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestNextRollback(t *testing.T) {
+	sel := Doc().Find("h1")
+	sel2 := sel.Next().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNext2(t *testing.T) {
+	sel := Doc().Find(".close").Next()
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestNextNone(t *testing.T) {
+	sel := Doc().Find("small").Next()
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestNextFiltered(t *testing.T) {
+	sel := Doc().Find(".container-fluid").NextFiltered("div")
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestNextFilteredInvalid(t *testing.T) {
+	sel := Doc().Find(".container-fluid").NextFiltered("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestNextFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.NextFiltered("div").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextFiltered2(t *testing.T) {
+	sel := Doc().Find(".container-fluid").NextFiltered("[ng-view]")
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestPrev(t *testing.T) {
+	sel := Doc().Find(".red").Prev()
+	assertLength(t, sel.Nodes, 1)
+	assertClass(t, sel, "green")
+}
+
+func TestPrevRollback(t *testing.T) {
+	sel := Doc().Find(".red")
+	sel2 := sel.Prev().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestPrev2(t *testing.T) {
+	sel := Doc().Find(".row-fluid").Prev()
+	assertLength(t, sel.Nodes, 5)
+}
+
+func TestPrevNone(t *testing.T) {
+	sel := Doc().Find("h2").Prev()
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestPrevFiltered(t *testing.T) {
+	sel := Doc().Find(".row-fluid").PrevFiltered(".row-fluid")
+	assertLength(t, sel.Nodes, 5)
+}
+
+func TestPrevFilteredInvalid(t *testing.T) {
+	sel := Doc().Find(".row-fluid").PrevFiltered("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestPrevFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".row-fluid")
+	sel2 := sel.PrevFiltered(".row-fluid").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextAll(t *testing.T) {
+	sel := Doc().Find("#cf2 div:nth-child(1)").NextAll()
+	assertLength(t, sel.Nodes, 3)
+}
+
+func TestNextAllRollback(t *testing.T) {
+	sel := Doc().Find("#cf2 div:nth-child(1)")
+	sel2 := sel.NextAll().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextAll2(t *testing.T) {
+	sel := Doc().Find("div[ng-cloak]").NextAll()
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestNextAllNone(t *testing.T) {
+	sel := Doc().Find(".footer").NextAll()
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestNextAllFiltered(t *testing.T) {
+	sel := Doc().Find("#cf2 .row-fluid").NextAllFiltered("[ng-cloak]")
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestNextAllFilteredInvalid(t *testing.T) {
+	sel := Doc().Find("#cf2 .row-fluid").NextAllFiltered("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestNextAllFilteredRollback(t *testing.T) {
+	sel := Doc().Find("#cf2 .row-fluid")
+	sel2 := sel.NextAllFiltered("[ng-cloak]").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextAllFiltered2(t *testing.T) {
+	sel := Doc().Find(".close").NextAllFiltered("h4")
+	assertLength(t, sel.Nodes, 1)
+}
+
+func TestPrevAll(t *testing.T) {
+	sel := Doc().Find("[ng-view]").PrevAll()
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestPrevAllOrder(t *testing.T) {
+	sel := Doc().Find("[ng-view]").PrevAll()
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel, "#cf4", "#cf3")
+}
+
+func TestPrevAllRollback(t *testing.T) {
+	sel := Doc().Find("[ng-view]")
+	sel2 := sel.PrevAll().End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestPrevAll2(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter").PrevAll()
+	assertLength(t, sel.Nodes, 6)
+}
+
+func TestPrevAllFiltered(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter").PrevAllFiltered(".pvk-content")
+	assertLength(t, sel.Nodes, 3)
+}
+
+func TestPrevAllFilteredInvalid(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter").PrevAllFiltered("")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestPrevAllFilteredRollback(t *testing.T) {
+	sel := Doc().Find(".pvk-gutter")
+	sel2 := sel.PrevAllFiltered(".pvk-content").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextUntil(t *testing.T) {
+	sel := Doc().Find(".alert a").NextUntil("p")
+	assertLength(t, sel.Nodes, 1)
+	assertSelectionIs(t, sel, "h4")
+}
+
+func TestNextUntilInvalid(t *testing.T) {
+	sel := Doc().Find(".alert a").NextUntil("")
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestNextUntil2(t *testing.T) {
+	sel := Doc().Find("#cf2-1").NextUntil("[ng-cloak]")
+	assertLength(t, sel.Nodes, 1)
+	assertSelectionIs(t, sel, "#cf2-2")
+}
+
+func TestNextUntilOrder(t *testing.T) {
+	sel := Doc().Find("#cf2-1").NextUntil("#cf2-4")
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel, "#cf2-2", "#cf2-3")
+}
+
+func TestNextUntilRollback(t *testing.T) {
+	sel := Doc().Find("#cf2-1")
+	sel2 := sel.PrevUntil("#cf2-4").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextUntilSelection(t *testing.T) {
+	sel := Doc2().Find("#n2")
+	sel2 := Doc2().Find("#n4")
+	sel2 = sel.NextUntilSelection(sel2)
+	assertLength(t, sel2.Nodes, 1)
+	assertSelectionIs(t, sel2, "#n3")
+}
+
+func TestNextUntilSelectionRollback(t *testing.T) {
+	sel := Doc2().Find("#n2")
+	sel2 := Doc2().Find("#n4")
+	sel2 = sel.NextUntilSelection(sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextUntilNodes(t *testing.T) {
+	sel := Doc2().Find("#n2")
+	sel2 := Doc2().Find("#n5")
+	sel2 = sel.NextUntilNodes(sel2.Nodes...)
+	assertLength(t, sel2.Nodes, 2)
+	assertSelectionIs(t, sel2, "#n3", "#n4")
+}
+
+func TestNextUntilNodesRollback(t *testing.T) {
+	sel := Doc2().Find("#n2")
+	sel2 := Doc2().Find("#n5")
+	sel2 = sel.NextUntilNodes(sel2.Nodes...).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestPrevUntil(t *testing.T) {
+	sel := Doc().Find(".alert p").PrevUntil("a")
+	assertLength(t, sel.Nodes, 1)
+	assertSelectionIs(t, sel, "h4")
+}
+
+func TestPrevUntilInvalid(t *testing.T) {
+	sel := Doc().Find(".alert p").PrevUntil("")
+	assertLength(t, sel.Nodes, 2)
+}
+
+func TestPrevUntil2(t *testing.T) {
+	sel := Doc().Find("[ng-cloak]").PrevUntil(":not([ng-cloak])")
+	assertLength(t, sel.Nodes, 1)
+	assertSelectionIs(t, sel, "[ng-cloak]")
+}
+
+func TestPrevUntilOrder(t *testing.T) {
+	sel := Doc().Find("#cf2-4").PrevUntil("#cf2-1")
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel, "#cf2-3", "#cf2-2")
+}
+
+func TestPrevUntilRollback(t *testing.T) {
+	sel := Doc().Find("#cf2-4")
+	sel2 := sel.PrevUntil("#cf2-1").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestPrevUntilSelection(t *testing.T) {
+	sel := Doc2().Find("#n4")
+	sel2 := Doc2().Find("#n2")
+	sel2 = sel.PrevUntilSelection(sel2)
+	assertLength(t, sel2.Nodes, 1)
+	assertSelectionIs(t, sel2, "#n3")
+}
+
+func TestPrevUntilSelectionRollback(t *testing.T) {
+	sel := Doc2().Find("#n4")
+	sel2 := Doc2().Find("#n2")
+	sel2 = sel.PrevUntilSelection(sel2).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestPrevUntilNodes(t *testing.T) {
+	sel := Doc2().Find("#n5")
+	sel2 := Doc2().Find("#n2")
+	sel2 = sel.PrevUntilNodes(sel2.Nodes...)
+	assertLength(t, sel2.Nodes, 2)
+	assertSelectionIs(t, sel2, "#n4", "#n3")
+}
+
+func TestPrevUntilNodesRollback(t *testing.T) {
+	sel := Doc2().Find("#n5")
+	sel2 := Doc2().Find("#n2")
+	sel2 = sel.PrevUntilNodes(sel2.Nodes...).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextFilteredUntil(t *testing.T) {
+	sel := Doc2().Find(".two").NextFilteredUntil(".even", ".six")
+	assertLength(t, sel.Nodes, 4)
+	assertSelectionIs(t, sel, "#n3", "#n5", "#nf3", "#nf5")
+}
+
+func TestNextFilteredUntilInvalid(t *testing.T) {
+	sel := Doc2().Find(".two").NextFilteredUntil("", "")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestNextFilteredUntilRollback(t *testing.T) {
+	sel := Doc2().Find(".two")
+	sel2 := sel.NextFilteredUntil(".even", ".six").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestNextFilteredUntilSelection(t *testing.T) {
+	sel := Doc2().Find(".even")
+	sel2 := Doc2().Find(".five")
+	sel = sel.NextFilteredUntilSelection(".even", sel2)
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel, "#n3", "#nf3")
+}
+
+func TestNextFilteredUntilSelectionRollback(t *testing.T) {
+	sel := Doc2().Find(".even")
+	sel2 := Doc2().Find(".five")
+	sel3 := sel.NextFilteredUntilSelection(".even", sel2).End()
+	assertEqual(t, sel, sel3)
+}
+
+func TestNextFilteredUntilNodes(t *testing.T) {
+	sel := Doc2().Find(".even")
+	sel2 := Doc2().Find(".four")
+	sel = sel.NextFilteredUntilNodes(".odd", sel2.Nodes...)
+	assertLength(t, sel.Nodes, 4)
+	assertSelectionIs(t, sel, "#n2", "#n6", "#nf2", "#nf6")
+}
+
+func TestNextFilteredUntilNodesRollback(t *testing.T) {
+	sel := Doc2().Find(".even")
+	sel2 := Doc2().Find(".four")
+	sel3 := sel.NextFilteredUntilNodes(".odd", sel2.Nodes...).End()
+	assertEqual(t, sel, sel3)
+}
+
+func TestPrevFilteredUntil(t *testing.T) {
+	sel := Doc2().Find(".five").PrevFilteredUntil(".odd", ".one")
+	assertLength(t, sel.Nodes, 4)
+	assertSelectionIs(t, sel, "#n4", "#n2", "#nf4", "#nf2")
+}
+
+func TestPrevFilteredUntilInvalid(t *testing.T) {
+	sel := Doc2().Find(".five").PrevFilteredUntil("", "")
+	assertLength(t, sel.Nodes, 0)
+}
+
+func TestPrevFilteredUntilRollback(t *testing.T) {
+	sel := Doc2().Find(".four")
+	sel2 := sel.PrevFilteredUntil(".odd", ".one").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestPrevFilteredUntilSelection(t *testing.T) {
+	sel := Doc2().Find(".odd")
+	sel2 := Doc2().Find(".two")
+	sel = sel.PrevFilteredUntilSelection(".odd", sel2)
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel, "#n4", "#nf4")
+}
+
+func TestPrevFilteredUntilSelectionRollback(t *testing.T) {
+	sel := Doc2().Find(".even")
+	sel2 := Doc2().Find(".five")
+	sel3 := sel.PrevFilteredUntilSelection(".even", sel2).End()
+	assertEqual(t, sel, sel3)
+}
+
+func TestPrevFilteredUntilNodes(t *testing.T) {
+	sel := Doc2().Find(".even")
+	sel2 := Doc2().Find(".four")
+	sel = sel.PrevFilteredUntilNodes(".odd", sel2.Nodes...)
+	assertLength(t, sel.Nodes, 2)
+	assertSelectionIs(t, sel, "#n2", "#nf2")
+}
+
+func TestPrevFilteredUntilNodesRollback(t *testing.T) {
+	sel := Doc2().Find(".even")
+	sel2 := Doc2().Find(".four")
+	sel3 := sel.PrevFilteredUntilNodes(".odd", sel2.Nodes...).End()
+	assertEqual(t, sel, sel3)
+}
+
+func TestClosestItself(t *testing.T) {
+	sel := Doc2().Find(".three")
+	sel2 := sel.Closest(".row")
+	assertLength(t, sel2.Nodes, sel.Length())
+	assertSelectionIs(t, sel2, "#n3", "#nf3")
+}
+
+func TestClosestNoDupes(t *testing.T) {
+	sel := Doc().Find(".span12")
+	sel2 := sel.Closest(".pvk-content")
+	assertLength(t, sel2.Nodes, 1)
+	assertClass(t, sel2, "pvk-content")
+}
+
+func TestClosestNone(t *testing.T) {
+	sel := Doc().Find("h4")
+	sel2 := sel.Closest("a")
+	assertLength(t, sel2.Nodes, 0)
+}
+
+func TestClosestInvalid(t *testing.T) {
+	sel := Doc().Find("h4")
+	sel2 := sel.Closest("")
+	assertLength(t, sel2.Nodes, 0)
+}
+
+func TestClosestMany(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.Closest(".pvk-content")
+	assertLength(t, sel2.Nodes, 2)
+	assertSelectionIs(t, sel2, "#pc1", "#pc2")
+}
+
+func TestClosestRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.Closest(".pvk-content").End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestClosestSelectionItself(t *testing.T) {
+	sel := Doc2().Find(".three")
+	sel2 := sel.ClosestSelection(Doc2().Find(".row"))
+	assertLength(t, sel2.Nodes, sel.Length())
+}
+
+func TestClosestSelectionNoDupes(t *testing.T) {
+	sel := Doc().Find(".span12")
+	sel2 := sel.ClosestSelection(Doc().Find(".pvk-content"))
+	assertLength(t, sel2.Nodes, 1)
+	assertClass(t, sel2, "pvk-content")
+}
+
+func TestClosestSelectionNone(t *testing.T) {
+	sel := Doc().Find("h4")
+	sel2 := sel.ClosestSelection(Doc().Find("a"))
+	assertLength(t, sel2.Nodes, 0)
+}
+
+func TestClosestSelectionMany(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.ClosestSelection(Doc().Find(".pvk-content"))
+	assertLength(t, sel2.Nodes, 2)
+	assertSelectionIs(t, sel2, "#pc1", "#pc2")
+}
+
+func TestClosestSelectionRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.ClosestSelection(Doc().Find(".pvk-content")).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestClosestNodesItself(t *testing.T) {
+	sel := Doc2().Find(".three")
+	sel2 := sel.ClosestNodes(Doc2().Find(".row").Nodes...)
+	assertLength(t, sel2.Nodes, sel.Length())
+}
+
+func TestClosestNodesNoDupes(t *testing.T) {
+	sel := Doc().Find(".span12")
+	sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...)
+	assertLength(t, sel2.Nodes, 1)
+	assertClass(t, sel2, "pvk-content")
+}
+
+func TestClosestNodesNone(t *testing.T) {
+	sel := Doc().Find("h4")
+	sel2 := sel.ClosestNodes(Doc().Find("a").Nodes...)
+	assertLength(t, sel2.Nodes, 0)
+}
+
+func TestClosestNodesMany(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...)
+	assertLength(t, sel2.Nodes, 2)
+	assertSelectionIs(t, sel2, "#pc1", "#pc2")
+}
+
+func TestClosestNodesRollback(t *testing.T) {
+	sel := Doc().Find(".container-fluid")
+	sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...).End()
+	assertEqual(t, sel, sel2)
+}
+
+func TestIssue26(t *testing.T) {
+	img1 := `<img src="assets/images/gallery/thumb-1.jpg" alt="150x150" />`
+	img2 := `<img alt="150x150" src="assets/images/gallery/thumb-1.jpg" />`
+	cases := []struct {
+		s string
+		l int
+	}{
+		{s: img1 + img2, l: 2},
+		{s: img1, l: 1},
+		{s: img2, l: 1},
+	}
+	for _, c := range cases {
+		doc, err := NewDocumentFromReader(strings.NewReader(c.s))
+		if err != nil {
+			t.Fatal(err)
+		}
+		sel := doc.Find("img[src]")
+		assertLength(t, sel.Nodes, c.l)
+	}
+}

+ 141 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/type.go

@@ -0,0 +1,141 @@
+package goquery
+
+import (
+	"errors"
+	"io"
+	"net/http"
+	"net/url"
+
+	"github.com/andybalholm/cascadia"
+
+	"golang.org/x/net/html"
+)
+
+// Document represents an HTML document to be manipulated. Unlike jQuery, which
+// is loaded as part of a DOM document, and thus acts upon its containing
+// document, GoQuery doesn't know which HTML document to act upon. So it needs
+// to be told, and that's what the Document class is for. It holds the root
+// document node to manipulate, and can make selections on this document.
+type Document struct {
+	*Selection
+	Url      *url.URL
+	rootNode *html.Node
+}
+
+// NewDocumentFromNode is a Document constructor that takes a root html Node
+// as argument.
+func NewDocumentFromNode(root *html.Node) *Document {
+	return newDocument(root, nil)
+}
+
+// NewDocument is a Document constructor that takes a string URL as argument.
+// It loads the specified document, parses it, and stores the root Document
+// node, ready to be manipulated.
+//
+// Deprecated: Use the net/http standard library package to make the request
+// and validate the response before calling goquery.NewDocumentFromReader
+// with the response's body.
+func NewDocument(url string) (*Document, error) {
+	// Load the URL
+	res, e := http.Get(url)
+	if e != nil {
+		return nil, e
+	}
+	return NewDocumentFromResponse(res)
+}
+
+// NewDocumentFromReader returns a Document from an io.Reader.
+// It returns an error as second value if the reader's data cannot be parsed
+// as html. It does not check if the reader is also an io.Closer, the
+// provided reader is never closed by this call. It is the responsibility
+// of the caller to close it if required.
+func NewDocumentFromReader(r io.Reader) (*Document, error) {
+	root, e := html.Parse(r)
+	if e != nil {
+		return nil, e
+	}
+	return newDocument(root, nil), nil
+}
+
+// NewDocumentFromResponse is another Document constructor that takes an http response as argument.
+// It loads the specified response's document, parses it, and stores the root Document
+// node, ready to be manipulated. The response's body is closed on return.
+//
+// Deprecated: Use goquery.NewDocumentFromReader with the response's body.
+func NewDocumentFromResponse(res *http.Response) (*Document, error) {
+	if res == nil {
+		return nil, errors.New("Response is nil")
+	}
+	defer res.Body.Close()
+	if res.Request == nil {
+		return nil, errors.New("Response.Request is nil")
+	}
+
+	// Parse the HTML into nodes
+	root, e := html.Parse(res.Body)
+	if e != nil {
+		return nil, e
+	}
+
+	// Create and fill the document
+	return newDocument(root, res.Request.URL), nil
+}
+
+// CloneDocument creates a deep-clone of a document.
+func CloneDocument(doc *Document) *Document {
+	return newDocument(cloneNode(doc.rootNode), doc.Url)
+}
+
+// Private constructor, make sure all fields are correctly filled.
+func newDocument(root *html.Node, url *url.URL) *Document {
+	// Create and fill the document
+	d := &Document{nil, url, root}
+	d.Selection = newSingleSelection(root, d)
+	return d
+}
+
+// Selection represents a collection of nodes matching some criteria. The
+// initial Selection can be created by using Document.Find, and then
+// manipulated using the jQuery-like chainable syntax and methods.
+type Selection struct {
+	Nodes    []*html.Node
+	document *Document
+	prevSel  *Selection
+}
+
+// Helper constructor to create an empty selection
+func newEmptySelection(doc *Document) *Selection {
+	return &Selection{nil, doc, nil}
+}
+
+// Helper constructor to create a selection of only one node
+func newSingleSelection(node *html.Node, doc *Document) *Selection {
+	return &Selection{[]*html.Node{node}, doc, nil}
+}
+
+// Matcher is an interface that defines the methods to match
+// HTML nodes against a compiled selector string. Cascadia's
+// Selector implements this interface.
+type Matcher interface {
+	Match(*html.Node) bool
+	MatchAll(*html.Node) []*html.Node
+	Filter([]*html.Node) []*html.Node
+}
+
+// compileMatcher compiles the selector string s and returns
+// the corresponding Matcher. If s is an invalid selector string,
+// it returns a Matcher that fails all matches.
+func compileMatcher(s string) Matcher {
+	cs, err := cascadia.Compile(s)
+	if err != nil {
+		return invalidMatcher{}
+	}
+	return cs
+}
+
+// invalidMatcher is a Matcher that always fails to match.
+type invalidMatcher struct{}
+
+func (invalidMatcher) Match(n *html.Node) bool             { return false }
+func (invalidMatcher) MatchAll(n *html.Node) []*html.Node  { return nil }
+func (invalidMatcher) Filter(ns []*html.Node) []*html.Node { return nil }

+ 202 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/type_test.go

@@ -0,0 +1,202 @@
+package goquery
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"golang.org/x/net/html"
+)
+
+// Test helper functions and members
+var doc *Document
+var doc2 *Document
+var doc3 *Document
+var docB *Document
+var docW *Document
+
+func Doc() *Document {
+	if doc == nil {
+		doc = loadDoc("page.html")
+	}
+	return doc
+}
+
+func Doc2() *Document {
+	if doc2 == nil {
+		doc2 = loadDoc("page2.html")
+	}
+	return doc2
+}
+
+func Doc2Clone() *Document {
+	return CloneDocument(Doc2())
+}
+
+func Doc3() *Document {
+	if doc3 == nil {
+		doc3 = loadDoc("page3.html")
+	}
+	return doc3
+}
+
+func Doc3Clone() *Document {
+	return CloneDocument(Doc3())
+}
+
+func DocB() *Document {
+	if docB == nil {
+		docB = loadDoc("gotesting.html")
+	}
+	return docB
+}
+
+func DocW() *Document {
+	if docW == nil {
+		docW = loadDoc("gowiki.html")
+	}
+	return docW
+}
+
+func assertLength(t *testing.T, nodes []*html.Node, length int) {
+	if len(nodes) != length {
+		t.Errorf("Expected %d nodes, found %d.", length, len(nodes))
+		for i, n := range nodes {
+			t.Logf("Node %d: %+v.", i, n)
+		}
+	}
+}
+
+func assertClass(t *testing.T, sel *Selection, class string) {
+	if !sel.HasClass(class) {
+		t.Errorf("Expected node to have class %s, found %+v.", class, sel.Get(0))
+	}
+}
+
+func assertPanic(t *testing.T) {
+	if e := recover(); e == nil {
+		t.Error("Expected a panic.")
+	}
+}
+
+func assertEqual(t *testing.T, s1 *Selection, s2 *Selection) {
+	if s1 != s2 {
+		t.Error("Expected selection objects to be the same.")
+	}
+}
+
+func assertSelectionIs(t *testing.T, sel *Selection, is ...string) {
+	for i := 0; i < sel.Length(); i++ {
+		if !sel.Eq(i).Is(is[i]) {
+			t.Errorf("Expected node %d to be %s, found %+v", i, is[i], sel.Get(i))
+		}
+	}
+}
+
+func printSel(t *testing.T, sel *Selection) {
+	if testing.Verbose() {
+		h, err := sel.Html()
+		if err != nil {
+			t.Fatal(err)
+		}
+		t.Log(h)
+	}
+}
+
+func loadDoc(page string) *Document {
+	var f *os.File
+	var e error
+
+	if f, e = os.Open(fmt.Sprintf("./testdata/%s", page)); e != nil {
+		panic(e.Error())
+	}
+	defer f.Close()
+
+	var node *html.Node
+	if node, e = html.Parse(f); e != nil {
+		panic(e.Error())
+	}
+	return NewDocumentFromNode(node)
+}
+
+func TestNewDocument(t *testing.T) {
+	if f, e := os.Open("./testdata/page.html"); e != nil {
+		t.Error(e.Error())
+	} else {
+		defer f.Close()
+		if node, e := html.Parse(f); e != nil {
+			t.Error(e.Error())
+		} else {
+			doc = NewDocumentFromNode(node)
+		}
+	}
+}
+
+func TestNewDocumentFromReader(t *testing.T) {
+	cases := []struct {
+		src string
+		err bool
+		sel string
+		cnt int
+	}{
+		0: {
+			src: `
+<html>
+<head>
+<title>Test</title>
+<body>
+<h1>Hi</h1>
+</body>
+</html>`,
+			sel: "h1",
+			cnt: 1,
+		},
+		1: {
+			// Actually pretty hard to make html.Parse return an error
+			// based on content...
+			src: `<html><body><aef<eqf>>>qq></body></ht>`,
+		},
+	}
+	buf := bytes.NewBuffer(nil)
+
+	for i, c := range cases {
+		buf.Reset()
+		buf.WriteString(c.src)
+
+		d, e := NewDocumentFromReader(buf)
+		if (e != nil) != c.err {
+			if c.err {
+				t.Errorf("[%d] - expected error, got none", i)
+			} else {
+				t.Errorf("[%d] - expected no error, got %s", i, e)
+			}
+		}
+		if c.sel != "" {
+			s := d.Find(c.sel)
+			if s.Length() != c.cnt {
+				t.Errorf("[%d] - expected %d nodes, found %d", i, c.cnt, s.Length())
+			}
+		}
+	}
+}
+
+func TestNewDocumentFromResponseNil(t *testing.T) {
+	_, e := NewDocumentFromResponse(nil)
+	if e == nil {
+		t.Error("Expected error, got none")
+	}
+}
+
+func TestIssue103(t *testing.T) {
+	d, err := NewDocumentFromReader(strings.NewReader("<html><title>Scientists Stored These Images in DNA—Then Flawlessly Retrieved Them</title></html>"))
+	if err != nil {
+		t.Error(err)
+	}
+	text := d.Find("title").Text()
+	for i, r := range text {
+		t.Logf("%d: %d - %q\n", i, r, string(r))
+	}
+	t.Log(text)
+}

+ 161 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/utilities.go

@@ -0,0 +1,161 @@
+package goquery
+
+import (
+	"bytes"
+
+	"golang.org/x/net/html"
+)
+
+// used to determine if a set (map[*html.Node]bool) should be used
+// instead of iterating over a slice. The set uses more memory and
+// is slower than slice iteration for small N.
+const minNodesForSet = 1000
+
+var nodeNames = []string{
+	html.ErrorNode:    "#error",
+	html.TextNode:     "#text",
+	html.DocumentNode: "#document",
+	html.CommentNode:  "#comment",
+}
+
+// NodeName returns the node name of the first element in the selection.
+// It tries to behave in a similar way as the DOM's nodeName property
+// (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName).
+//
+// Go's net/html package defines the following node types, listed with
+// the corresponding returned value from this function:
+//
+//     ErrorNode : #error
+//     TextNode : #text
+//     DocumentNode : #document
+//     ElementNode : the element's tag name
+//     CommentNode : #comment
+//     DoctypeNode : the name of the document type
+//
+func NodeName(s *Selection) string {
+	if s.Length() == 0 {
+		return ""
+	}
+	switch n := s.Get(0); n.Type {
+	case html.ElementNode, html.DoctypeNode:
+		return n.Data
+	default:
+		if n.Type >= 0 && int(n.Type) < len(nodeNames) {
+			return nodeNames[n.Type]
+		}
+		return ""
+	}
+}
+
+// OuterHtml returns the outer HTML rendering of the first item in
+// the selection - that is, the HTML including the first element's
+// tag and attributes.
+//
+// Unlike InnerHtml, this is a function and not a method on the Selection,
+// because this is not a jQuery method (in javascript-land, this is
+// a property provided by the DOM).
+func OuterHtml(s *Selection) (string, error) {
+	var buf bytes.Buffer
+
+	if s.Length() == 0 {
+		return "", nil
+	}
+	n := s.Get(0)
+	if err := html.Render(&buf, n); err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+// Loop through all container nodes to search for the target node.
+func sliceContains(container []*html.Node, contained *html.Node) bool {
+	for _, n := range container {
+		if nodeContains(n, contained) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Checks if the contained node is within the container node.
+func nodeContains(container *html.Node, contained *html.Node) bool {
+	// Check if the parent of the contained node is the container node, traversing
+	// upward until the top is reached, or the container is found.
+	for contained = contained.Parent; contained != nil; contained = contained.Parent {
+		if container == contained {
+			return true
+		}
+	}
+	return false
+}
+
+// Checks if the target node is in the slice of nodes.
+func isInSlice(slice []*html.Node, node *html.Node) bool {
+	return indexInSlice(slice, node) > -1
+}
+
+// Returns the index of the target node in the slice, or -1.
+func indexInSlice(slice []*html.Node, node *html.Node) int {
+	if node != nil {
+		for i, n := range slice {
+			if n == node {
+				return i
+			}
+		}
+	}
+	return -1
+}
+
+// Appends the new nodes to the target slice, making sure no duplicate is added.
+// There is no check to the original state of the target slice, so it may still
+// contain duplicates. The target slice is returned because append() may create
+// a new underlying array. If targetSet is nil, a local set is created with the
+// target if len(target) + len(nodes) is greater than minNodesForSet.
+func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node {
+	// if there are not that many nodes, don't use the map, faster to just use nested loops
+	// (unless a non-nil targetSet is passed, in which case the caller knows better).
+	if targetSet == nil && len(target)+len(nodes) < minNodesForSet {
+		for _, n := range nodes {
+			if !isInSlice(target, n) {
+				target = append(target, n)
+			}
+		}
+		return target
+	}
+
+	// if a targetSet is passed, then assume it is reliable, otherwise create one
+	// and initialize it with the current target contents.
+	if targetSet == nil {
+		targetSet = make(map[*html.Node]bool, len(target))
+		for _, n := range target {
+			targetSet[n] = true
+		}
+	}
+	for _, n := range nodes {
+		if !targetSet[n] {
+			target = append(target, n)
+			targetSet[n] = true
+		}
+	}
+
+	return target
+}
+
+// Loop through a selection, returning only those nodes that pass the predicate
+// function.
+func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) {
+	for i, n := range sel.Nodes {
+		if predicate(i, newSingleSelection(n, sel.document)) {
+			result = append(result, n)
+		}
+	}
+	return result
+}
+
+// Creates a new Selection object based on the specified nodes, and keeps the
+// source Selection object on the stack (linked list).
+func pushStack(fromSel *Selection, nodes []*html.Node) *Selection {
+	result := &Selection{nodes, fromSel.document, fromSel}
+	return result
+}

+ 128 - 0
spirit/common/src/github.com/PuerkitoBio/goquery/utilities_test.go

@@ -0,0 +1,128 @@
+package goquery
+
+import (
+	"reflect"
+	"sort"
+	"strings"
+	"testing"
+
+	"golang.org/x/net/html"
+)
+
+var allNodes = `<!doctype html>
+<html>
+	<head>
+		<meta a="b">
+	</head>
+	<body>
+		<p><!-- this is a comment -->
+		This is some text.
+		</p>
+		<div></div>
+		<h1 class="header"></h1>
+		<h2 class="header"></h2>
+	</body>
+</html>`
+
+func TestNodeName(t *testing.T) {
+	doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	n0 := doc.Nodes[0]
+	nDT := n0.FirstChild
+	sMeta := doc.Find("meta")
+	nMeta := sMeta.Get(0)
+	sP := doc.Find("p")
+	nP := sP.Get(0)
+	nComment := nP.FirstChild
+	nText := nComment.NextSibling
+
+	cases := []struct {
+		node *html.Node
+		typ  html.NodeType
+		want string
+	}{
+		{n0, html.DocumentNode, nodeNames[html.DocumentNode]},
+		{nDT, html.DoctypeNode, "html"},
+		{nMeta, html.ElementNode, "meta"},
+		{nP, html.ElementNode, "p"},
+		{nComment, html.CommentNode, nodeNames[html.CommentNode]},
+		{nText, html.TextNode, nodeNames[html.TextNode]},
+	}
+	for i, c := range cases {
+		got := NodeName(newSingleSelection(c.node, doc))
+		if c.node.Type != c.typ {
+			t.Errorf("%d: want type %v, got %v", i, c.typ, c.node.Type)
+		}
+		if got != c.want {
+			t.Errorf("%d: want %q, got %q", i, c.want, got)
+		}
+	}
+}
+
+func TestNodeNameMultiSel(t *testing.T) {
+	doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	in := []string{"p", "h1", "div"}
+	var out []string
+	doc.Find(strings.Join(in, ", ")).Each(func(i int, s *Selection) {
+		got := NodeName(s)
+		out = append(out, got)
+	})
+	sort.Strings(in)
+	sort.Strings(out)
+	if !reflect.DeepEqual(in, out) {
+		t.Errorf("want %v, got %v", in, out)
+	}
+}
+
+func TestOuterHtml(t *testing.T) {
+	doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	n0 := doc.Nodes[0]
+	nDT := n0.FirstChild
+	sMeta := doc.Find("meta")
+	sP := doc.Find("p")
+	nP := sP.Get(0)
+	nComment := nP.FirstChild
+	nText := nComment.NextSibling
+	sHeaders := doc.Find(".header")
+
+	cases := []struct {
+		node *html.Node
+		sel  *Selection
+		want string
+	}{
+		{nDT, nil, "<!DOCTYPE html>"}, // render makes DOCTYPE all caps
+		{nil, sMeta, `<meta a="b"/>`}, // and auto-closes the meta
+		{nil, sP, `<p><!-- this is a comment -->
+		This is some text.
+		</p>`},
+		{nComment, nil, "<!-- this is a comment -->"},
+		{nText, nil, `
+		This is some text.
+		`},
+		{nil, sHeaders, `<h1 class="header"></h1>`},
+	}
+	for i, c := range cases {
+		if c.sel == nil {
+			c.sel = newSingleSelection(c.node, doc)
+		}
+		got, err := OuterHtml(c.sel)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if got != c.want {
+			t.Errorf("%d: want %q, got %q", i, c.want, got)
+		}
+	}
+}

+ 2 - 0
spirit/common/src/github.com/alecthomas/log4go/.gitignore

@@ -0,0 +1,2 @@
+*.sw[op]
+.DS_Store

+ 13 - 0
spirit/common/src/github.com/alecthomas/log4go/LICENSE

@@ -0,0 +1,13 @@
+Copyright (c) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 14 - 0
spirit/common/src/github.com/alecthomas/log4go/README

@@ -0,0 +1,14 @@
+# This is an unmaintained fork, left only so it doesn't break imports.
+
+Please see http://log4go.googlecode.com/
+
+Installation:
+- Run `goinstall log4go.googlecode.com/hg`
+
+Usage:
+- Add the following import:
+import l4g "log4go.googlecode.com/hg"
+
+Acknowledgements:
+- pomack
+  For providing awesome patches to bring log4go up to the latest Go spec

+ 296 - 0
spirit/common/src/github.com/alecthomas/log4go/config.go

@@ -0,0 +1,296 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>.  All rights reserved.
+
+package log4go
+
+import (
+	"encoding/xml"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strconv"
+	"strings"
+)
+
+type xmlProperty struct {
+	Name  string `xml:"name,attr"`
+	Value string `xml:",chardata"`
+}
+
+type xmlFilter struct {
+	Enabled  string        `xml:"enabled,attr"`
+	Tag      string        `xml:"tag"`
+	Level    string        `xml:"level"`
+	Type     string        `xml:"type"`
+	Property []xmlProperty `xml:"property"`
+}
+
+type xmlLoggerConfig struct {
+	Filter []xmlFilter `xml:"filter"`
+}
+
+// Load XML configuration; see examples/example.xml for documentation
+func (log Logger) LoadConfiguration(filename string) {
+	log.Close()
+
+	// Open the configuration file
+	fd, err := os.Open(filename)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not open %q for reading: %s\n", filename, err)
+		os.Exit(1)
+	}
+
+	contents, err := ioutil.ReadAll(fd)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not read %q: %s\n", filename, err)
+		os.Exit(1)
+	}
+
+	xc := new(xmlLoggerConfig)
+	if err := xml.Unmarshal(contents, xc); err != nil {
+		fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not parse XML configuration in %q: %s\n", filename, err)
+		os.Exit(1)
+	}
+
+	for _, xmlfilt := range xc.Filter {
+		var filt LogWriter
+		var lvl Level
+		bad, good, enabled := false, true, false
+
+		// Check required children
+		if len(xmlfilt.Enabled) == 0 {
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required attribute %s for filter missing in %s\n", "enabled", filename)
+			bad = true
+		} else {
+			enabled = xmlfilt.Enabled != "false"
+		}
+		if len(xmlfilt.Tag) == 0 {
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "tag", filename)
+			bad = true
+		}
+		if len(xmlfilt.Type) == 0 {
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "type", filename)
+			bad = true
+		}
+		if len(xmlfilt.Level) == 0 {
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "level", filename)
+			bad = true
+		}
+
+		switch xmlfilt.Level {
+		case "FINEST":
+			lvl = FINEST
+		case "FINE":
+			lvl = FINE
+		case "DEBUG":
+			lvl = DEBUG
+		case "TRACE":
+			lvl = TRACE
+		case "INFO":
+			lvl = INFO
+		case "WARNING":
+			lvl = WARNING
+		case "ERROR":
+			lvl = ERROR
+		case "CRITICAL":
+			lvl = CRITICAL
+		default:
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter has unknown value in %s: %s\n", "level", filename, xmlfilt.Level)
+			bad = true
+		}
+
+		// Just so all of the required attributes are errored at the same time if missing
+		if bad {
+			os.Exit(1)
+		}
+
+		switch xmlfilt.Type {
+		case "console":
+			filt, good = xmlToConsoleLogWriter(filename, xmlfilt.Property, enabled)
+		case "file":
+			filt, good = xmlToFileLogWriter(filename, xmlfilt.Property, enabled)
+		case "xml":
+			filt, good = xmlToXMLLogWriter(filename, xmlfilt.Property, enabled)
+		case "socket":
+			filt, good = xmlToSocketLogWriter(filename, xmlfilt.Property, enabled)
+		default:
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not load XML configuration in %s: unknown filter type \"%s\"\n", filename, xmlfilt.Type)
+			os.Exit(1)
+		}
+
+		// Just so all of the required params are errored at the same time if wrong
+		if !good {
+			os.Exit(1)
+		}
+
+		// If we're disabled (syntax and correctness checks only), don't add to logger
+		if !enabled {
+			continue
+		}
+
+		log[xmlfilt.Tag] = &Filter{lvl, filt}
+	}
+}
+
+func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (*ConsoleLogWriter, bool) {
+
+	format := "[%D %T] [%L] (%S) %M"
+
+	// Parse properties
+	for _, prop := range props {
+		switch prop.Name {
+		case "format":
+			format = strings.Trim(prop.Value, " \r\n")
+		default:
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for console filter in %s\n", prop.Name, filename)
+		}
+	}
+
+	// If it's disabled, we're just checking syntax
+	if !enabled {
+		return nil, true
+	}
+
+	clw := NewConsoleLogWriter()
+	clw.SetFormat(format)
+
+	return clw, true
+}
+
+// Parse a number with K/M/G suffixes based on thousands (1000) or 2^10 (1024)
+func strToNumSuffix(str string, mult int) int {
+	num := 1
+	if len(str) > 1 {
+		switch str[len(str)-1] {
+		case 'G', 'g':
+			num *= mult
+			fallthrough
+		case 'M', 'm':
+			num *= mult
+			fallthrough
+		case 'K', 'k':
+			num *= mult
+			str = str[0 : len(str)-1]
+		}
+	}
+	parsed, _ := strconv.Atoi(str)
+	return parsed * num
+}
+func xmlToFileLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) {
+	file := ""
+	format := "[%D %T] [%L] (%S) %M"
+	maxlines := 0
+	maxsize := 0
+	daily := false
+	rotate := false
+
+	// Parse properties
+	for _, prop := range props {
+		switch prop.Name {
+		case "filename":
+			file = strings.Trim(prop.Value, " \r\n")
+		case "format":
+			format = strings.Trim(prop.Value, " \r\n")
+		case "maxlines":
+			maxlines = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000)
+		case "maxsize":
+			maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024)
+		case "daily":
+			daily = strings.Trim(prop.Value, " \r\n") != "false"
+		case "rotate":
+			rotate = strings.Trim(prop.Value, " \r\n") != "false"
+		default:
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename)
+		}
+	}
+
+	// Check properties
+	if len(file) == 0 {
+		fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "filename", filename)
+		return nil, false
+	}
+
+	// If it's disabled, we're just checking syntax
+	if !enabled {
+		return nil, true
+	}
+
+	flw := NewFileLogWriter(file, rotate)
+	flw.SetFormat(format)
+	flw.SetRotateLines(maxlines)
+	flw.SetRotateSize(maxsize)
+	flw.SetRotateDaily(daily)
+	return flw, true
+}
+
+func xmlToXMLLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) {
+	file := ""
+	maxrecords := 0
+	maxsize := 0
+	daily := false
+	rotate := false
+
+	// Parse properties
+	for _, prop := range props {
+		switch prop.Name {
+		case "filename":
+			file = strings.Trim(prop.Value, " \r\n")
+		case "maxrecords":
+			maxrecords = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000)
+		case "maxsize":
+			maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024)
+		case "daily":
+			daily = strings.Trim(prop.Value, " \r\n") != "false"
+		case "rotate":
+			rotate = strings.Trim(prop.Value, " \r\n") != "false"
+		default:
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for xml filter in %s\n", prop.Name, filename)
+		}
+	}
+
+	// Check properties
+	if len(file) == 0 {
+		fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for xml filter missing in %s\n", "filename", filename)
+		return nil, false
+	}
+
+	// If it's disabled, we're just checking syntax
+	if !enabled {
+		return nil, true
+	}
+
+	xlw := NewXMLLogWriter(file, rotate)
+	xlw.SetRotateLines(maxrecords)
+	xlw.SetRotateSize(maxsize)
+	xlw.SetRotateDaily(daily)
+	return xlw, true
+}
+
+func xmlToSocketLogWriter(filename string, props []xmlProperty, enabled bool) (SocketLogWriter, bool) {
+	endpoint := ""
+	protocol := "udp"
+
+	// Parse properties
+	for _, prop := range props {
+		switch prop.Name {
+		case "endpoint":
+			endpoint = strings.Trim(prop.Value, " \r\n")
+		case "protocol":
+			protocol = strings.Trim(prop.Value, " \r\n")
+		default:
+			fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename)
+		}
+	}
+
+	// Check properties
+	if len(endpoint) == 0 {
+		fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "endpoint", filename)
+		return nil, false
+	}
+
+	// If it's disabled, we're just checking syntax
+	if !enabled {
+		return nil, true
+	}
+
+	return NewSocketLogWriter(protocol, endpoint), true
+}

Some files were not shown because too many files changed in this diff