From 9e2db6a2e70494e349153091b1a4168b0e5e88a8 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 8 Aug 2017 11:49:45 +0200 Subject: Vendor --- Gopkg.lock | 21 + Gopkg.toml | 75 +++ metrics.go | 12 - vendor/github.com/sarnowski/mitigation/LICENSE | 13 + vendor/github.com/sarnowski/mitigation/README | 50 ++ .../github.com/sarnowski/mitigation/mitigation.go | 118 +++++ .../sarnowski/mitigation/mitigation_test.go | 173 +++++++ vendor/golang.org/x/tools/.gitattributes | 10 + vendor/golang.org/x/tools/.gitignore | 2 + vendor/golang.org/x/tools/AUTHORS | 3 + vendor/golang.org/x/tools/CONTRIBUTING.md | 31 ++ vendor/golang.org/x/tools/CONTRIBUTORS | 3 + vendor/golang.org/x/tools/LICENSE | 27 + vendor/golang.org/x/tools/PATENTS | 22 + vendor/golang.org/x/tools/README | 10 + vendor/golang.org/x/tools/blog/atom/atom.go | 61 +++ vendor/golang.org/x/tools/blog/blog.go | 424 ++++++++++++++++ vendor/golang.org/x/tools/codereview.cfg | 1 + vendor/golang.org/x/tools/present/args.go | 229 +++++++++ vendor/golang.org/x/tools/present/caption.go | 22 + vendor/golang.org/x/tools/present/code.go | 267 ++++++++++ vendor/golang.org/x/tools/present/code_test.go | 225 +++++++++ vendor/golang.org/x/tools/present/doc.go | 266 ++++++++++ vendor/golang.org/x/tools/present/html.go | 31 ++ vendor/golang.org/x/tools/present/iframe.go | 45 ++ vendor/golang.org/x/tools/present/image.go | 50 ++ vendor/golang.org/x/tools/present/link.go | 100 ++++ vendor/golang.org/x/tools/present/link_test.go | 40 ++ vendor/golang.org/x/tools/present/parse.go | 559 +++++++++++++++++++++ vendor/golang.org/x/tools/present/style.go | 167 ++++++ vendor/golang.org/x/tools/present/style_test.go | 124 +++++ vendor/golang.org/x/tools/present/video.go | 51 ++ 32 files changed, 3220 insertions(+), 12 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml delete mode 100644 metrics.go create mode 100644 vendor/github.com/sarnowski/mitigation/LICENSE create mode 100644 vendor/github.com/sarnowski/mitigation/README create mode 100644 vendor/github.com/sarnowski/mitigation/mitigation.go create mode 100644 vendor/github.com/sarnowski/mitigation/mitigation_test.go create mode 100644 vendor/golang.org/x/tools/.gitattributes create mode 100644 vendor/golang.org/x/tools/.gitignore create mode 100644 vendor/golang.org/x/tools/AUTHORS create mode 100644 vendor/golang.org/x/tools/CONTRIBUTING.md create mode 100644 vendor/golang.org/x/tools/CONTRIBUTORS create mode 100644 vendor/golang.org/x/tools/LICENSE create mode 100644 vendor/golang.org/x/tools/PATENTS create mode 100644 vendor/golang.org/x/tools/README create mode 100644 vendor/golang.org/x/tools/blog/atom/atom.go create mode 100644 vendor/golang.org/x/tools/blog/blog.go create mode 100644 vendor/golang.org/x/tools/codereview.cfg create mode 100644 vendor/golang.org/x/tools/present/args.go create mode 100644 vendor/golang.org/x/tools/present/caption.go create mode 100644 vendor/golang.org/x/tools/present/code.go create mode 100644 vendor/golang.org/x/tools/present/code_test.go create mode 100644 vendor/golang.org/x/tools/present/doc.go create mode 100644 vendor/golang.org/x/tools/present/html.go create mode 100644 vendor/golang.org/x/tools/present/iframe.go create mode 100644 vendor/golang.org/x/tools/present/image.go create mode 100644 vendor/golang.org/x/tools/present/link.go create mode 100644 vendor/golang.org/x/tools/present/link_test.go create mode 100644 vendor/golang.org/x/tools/present/parse.go create mode 100644 vendor/golang.org/x/tools/present/style.go create mode 100644 vendor/golang.org/x/tools/present/style_test.go create mode 100644 vendor/golang.org/x/tools/present/video.go diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..18e1c70 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,21 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/sarnowski/mitigation" + packages = ["."] + revision = "bff64e7420fb68c0efa9e92548d4538deed75ca2" + version = "1.0.3" + +[[projects]] + branch = "master" + name = "golang.org/x/tools" + packages = ["blog","blog/atom","present"] + revision = "f03b3350b7d6cb09e4221e7f2ecffaf41f89fce1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "eeff4fecf2a6d695a64109ea2b34350fc4712d1f3a8f5b1e82b5ac8368664a73" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..1937368 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,75 @@ + +## Gopkg.toml example (these lines may be deleted) + +## "metadata" defines metadata about the project that could be used by other independent +## systems. The metadata defined here will be ignored by dep. +# [metadata] +# key1 = "value that convey data to other systems" +# system1-data = "value that is used by a system" +# system2-data = "value that is used by another system" + +## "required" lists a set of packages (not projects) that must be included in +## Gopkg.lock. This list is merged with the set of packages imported by the current +## project. Use it when your project needs a package it doesn't explicitly import - +## including "main" packages. +# required = ["github.com/user/thing/cmd/thing"] + +## "ignored" lists a set of packages (not projects) that are ignored when +## dep statically analyzes source code. Ignored packages can be in this project, +## or in a dependency. +# ignored = ["github.com/user/project/badpkg"] + +## Constraints are rules for how directly imported projects +## may be incorporated into the depgraph. They are respected by +## dep whether coming from the Gopkg.toml of the current project or a dependency. +# [[constraint]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Recommended: the version constraint to enforce for the project. +## Only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: an alternate location (URL or import path) for the project's source. +# source = "https://github.com/myfork/package.git" +# +## "metadata" defines metadata about the dependency or override that could be used +## by other independent systems. The metadata defined here will be ignored by dep. +# [metadata] +# key1 = "value that convey data to other systems" +# system1-data = "value that is used by a system" +# system2-data = "value that is used by another system" + +## Overrides have the same structure as [[constraint]], but supersede all +## [[constraint]] declarations from all projects. Only [[override]] from +## the current project's are applied. +## +## Overrides are a sledgehammer. Use them only as a last resort. +# [[override]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Optional: specifying a version constraint override will cause all other +## constraints on this project to be ignored; only the overridden constraint +## need be satisfied. +## Again, only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: specifying an alternate source location as an override will +## enforce that the alternate location is used for that project, regardless of +## what source location any dependent projects specify. +# source = "https://github.com/myfork/package.git" + + + +[[constraint]] + name = "github.com/sarnowski/mitigation" + version = "1.0.3" + +[[constraint]] + branch = "master" + name = "golang.org/x/tools" diff --git a/metrics.go b/metrics.go deleted file mode 100644 index 333a9fd..0000000 --- a/metrics.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "net/http" - _ "net/http/pprof" - - "github.com/prometheus/client_golang/prometheus" -) - -func init() { - http.Handle("/metrics", prometheus.Handler()) -} diff --git a/vendor/github.com/sarnowski/mitigation/LICENSE b/vendor/github.com/sarnowski/mitigation/LICENSE new file mode 100644 index 0000000..1c949b5 --- /dev/null +++ b/vendor/github.com/sarnowski/mitigation/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2012 Tobias Sarnowski + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/sarnowski/mitigation/README b/vendor/github.com/sarnowski/mitigation/README new file mode 100644 index 0000000..e7779eb --- /dev/null +++ b/vendor/github.com/sarnowski/mitigation/README @@ -0,0 +1,50 @@ +mitigation for go applications +============================================================================= + +Package mitigation provides the possibility to prevent damage caused by bugs +or exploits. + + +Techniques +----------------------------------------------------------------------------- + +The package uses multiple techniques to mitigate damage: + + - privilege revocation: switch to an unprivileged user + - chroot jail: restrict filesystem access + - defined environment: reset all environment variables + +Enables the ability to implement system-supported privilege seperation. + + +Installation +----------------------------------------------------------------------------- + +To install and use the mitigation execute the following command: + + go get github.com/sarnowski/mitigation + + +Documentation +----------------------------------------------------------------------------- + +Documentation is provided in the go way and is accessible through "godoc". +You will find explanation, requirements and examples. + + +License +----------------------------------------------------------------------------- + +Copyright (c) 2012 Tobias Sarnowski + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/sarnowski/mitigation/mitigation.go b/vendor/github.com/sarnowski/mitigation/mitigation.go new file mode 100644 index 0000000..a1110e4 --- /dev/null +++ b/vendor/github.com/sarnowski/mitigation/mitigation.go @@ -0,0 +1,118 @@ +/* +Package mitigation provides the possibility to prevent damage through bugs or exploits. + +The package uses multiple techniques to mitigate damage: + - privilege revocation: switch to an unprivileged user + - chroot jail: restrict filesystem access + - defined environment: reset all environment variables + +The following prerequisites are nessecary: + - The application must run as root + - You need to provide a valid user id + - You need to provide a valid group id + - You need to provide an existing path + +Activate() will not return any error. It will panic as soon as anything +goes wrong because there is no good way to recover. To provide a sensible +fallback you can use the CanActivate() function. + +WARNING: Windows is not supported. Windows has no equivalents for the used +techniques. + +WARNING: Linux is not POSIX compatible and therefor setuid() only changes the +user ID of the current thread. At the time, there is no way to safely use +this within go as there may already be other threads spawned at the time +this library is called. More about this issue here: + http://code.google.com/p/go/issues/detail?id=1435 + http://groups.google.com/group/golang-nuts/browse_thread/thread/059597aafdd84a0e + +The following table summarizes the behaviours: + openbsd: safe + freebsd: safe + darwin: safe + linux: unsafe + windows: not supported + +*/ +package mitigation + +import ( + "os" + "runtime" + "syscall" +) + +// Checks if it is possible to activate the mitigation. +func CanActivate() bool { + if runtime.GOOS == "windows" || runtime.GOARCH == "arm" { + return false + } + + uid := syscall.Getuid() + return uid == 0 +} + +// Activates the mitigation measurements. +func Activate(uid int, gid int, path string) { + if !CanActivate() { + panic("Cannot activate mitigation measurements!") + } + + // chroot directory + err := syscall.Chroot(path) + if err != nil { + panic(err) + } + + // change directory to new / + err = syscall.Chdir("/") + if err != nil { + panic(err) + } + + // drop all other groups + err = syscall.Setgroups([]int{gid}) + if err != nil { + panic(err) + } + + // verify the empty group list + gids, err := syscall.Getgroups() + if err != nil { + panic("Could not read groups!") + } + if len(gids) > 1 { + panic("Could not drop groups!") + } else if len(gids) == 1 { + if gids[0] != gid { + panic("Could not drop foreign groups!") + } + } + + // change group + err = syscall.Setgid(gid) + if err != nil { + panic(err) + } + + // verify the group change + ngid := syscall.Getgid() + if ngid != gid { + panic("Could not change group id!") + } + + // change user + err = syscall.Setuid(uid) + if err != nil { + panic(err) + } + + // verify the user change + nuid := syscall.Getuid() + if nuid != uid { + panic("Could not change user id!") + } + + // now drop all environment variables + os.Clearenv() +} diff --git a/vendor/github.com/sarnowski/mitigation/mitigation_test.go b/vendor/github.com/sarnowski/mitigation/mitigation_test.go new file mode 100644 index 0000000..0fafeab --- /dev/null +++ b/vendor/github.com/sarnowski/mitigation/mitigation_test.go @@ -0,0 +1,173 @@ +package mitigation + +import ( + "io/ioutil" + "log" + "net" + "net/http" + "os" + "os/user" + "runtime" + "strconv" + "syscall" + "testing" +) + +const ( + TEST_UID = 1000 + TEST_GID = 1000 + + TEST_ROUTINES_COUNT = 100 +) + +// The test will only work when running as root. +func TestCanActivate(t *testing.T) { + if !CanActivate() { + t.Fatal("Tests must run as root!") + } +} + +// The test will only work when running as root. +func TestActivate(t *testing.T) { + // create temporary directory to test chrooting + tmp, err := ioutil.TempDir("", "mitigationtest") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmp) + err = syscall.Chmod(tmp, 0755) + if err != nil { + t.Fatal("Could not change temporary directory permissions!") + } + + // improve cpu usage to test for broken os implementations of setuid() + runtime.GOMAXPROCS(2) + + // create some go routines as root to later test them + var sync chan bool = make(chan bool) + for i := 0; i < TEST_ROUTINES_COUNT; i++ { + go func() { + // no op + sync <- true + }() + } + for i := 0; i < TEST_ROUTINES_COUNT; i++ { + <-sync + } + + // modify environment + err = os.Setenv("malicous_env", "bad string") + if err != nil { + t.Fatal("Cannot setup environment variables!") + } + if len(os.Environ()) == 0 { + t.Fatal("Environ() or Setenv() are broken!") + } + + // do it! + Activate(TEST_UID, TEST_GID, tmp) + + // verify uids + uid := syscall.Getuid() + if uid != TEST_UID { + t.Error("Failed to change UID") + } + euid := syscall.Geteuid() + if euid != TEST_UID { + t.Error("Failed to change EUID") + } + + // verify gid + gid := syscall.Getgid() + if gid != TEST_GID { + t.Error("Failed to change GID") + } + + // verify groups + gids, err := syscall.Getgroups() + if err != nil { + t.Fatal("Could not get group list") + } + if len(gids) > 1 { + t.Error("Not all groups are dropped!") + } else if len(gids) == 1 { + if gids[0] != TEST_GID { + t.Error("Not all foreign groups are dropped!") + } + } + + // verify directory + dh, err := os.Open("/") + if err != nil { + t.Fatal("Cannot open my root directory", err) + } + files, err := dh.Readdir(-1) + if err != nil { + t.Fatal("Cannot read my root directory") + } + if len(files) > 0 { + t.Error("Root not changed to empty temporary directory!") + } + + // verify environment + if len(os.Environ()) > 0 { + t.Error("Environment variables found!") + } + + // test setuid() behaviour + var results chan int = make(chan int) + + // start multiple goroutines, in a good OS, all all routines + // should be switched to the new user + for i := 0; i < TEST_ROUTINES_COUNT; i++ { + go func() { + results <- syscall.Getuid() + }() + } + + // check the results + for i := 0; i < TEST_ROUTINES_COUNT; i++ { + uid := <-results + if uid != TEST_UID { + t.Error("false uid: ", uid, " (you are using an unsafe os, read the package documentation!)") + break + } + } +} + +func ExampleActivate() { + // prepare application execution and allocate ressources with + // root privileges (e.g. open port 80 for an http server) + listener, _ := net.Listen("tcp", ":80") + + + // on OpenBSD, the "www" user is dedicated to serve the + // /var/www/htdocs directory in a chrooted way + wwwUser, _ := user.Lookup("www") + uid, _ := strconv.Atoi(wwwUser.Uid) + gid, _ := strconv.Atoi(wwwUser.Gid) + + // now drop all privileges and chroot! + Activate(uid, gid, "/var/www/htdocs") + + + // The application is now chrooted and only runs with "www" + // privileges. + http.Serve(listener, http.FileServer(http.Dir("/"))) +} + +func ExampleCanActivate() { + workDir := "/var/www" + + if CanActivate() { + // activate the mitigation and reset work directory to "/" + Activate(67, 67, workDir) + workDir = "/" + } else { + // we can handle this but log a warning + log.Println("WARNING: Cannot activate mitigation!") + } + + // use "workDir" to address our files + log.Println("Working directory: ", workDir) +} diff --git a/vendor/golang.org/x/tools/.gitattributes b/vendor/golang.org/x/tools/.gitattributes new file mode 100644 index 0000000..d2f212e --- /dev/null +++ b/vendor/golang.org/x/tools/.gitattributes @@ -0,0 +1,10 @@ +# Treat all files in this repo as binary, with no git magic updating +# line endings. Windows users contributing to Go will need to use a +# modern version of git and editors capable of LF line endings. +# +# We'll prevent accidental CRLF line endings from entering the repo +# via the git-review gofmt checks. +# +# See golang.org/issue/9281 + +* -text diff --git a/vendor/golang.org/x/tools/.gitignore b/vendor/golang.org/x/tools/.gitignore new file mode 100644 index 0000000..5a9d62e --- /dev/null +++ b/vendor/golang.org/x/tools/.gitignore @@ -0,0 +1,2 @@ +# Add no patterns to .gitignore except for files generated by the build. +last-change diff --git a/vendor/golang.org/x/tools/AUTHORS b/vendor/golang.org/x/tools/AUTHORS new file mode 100644 index 0000000..15167cd --- /dev/null +++ b/vendor/golang.org/x/tools/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/tools/CONTRIBUTING.md b/vendor/golang.org/x/tools/CONTRIBUTING.md new file mode 100644 index 0000000..88dff59 --- /dev/null +++ b/vendor/golang.org/x/tools/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to Go + +Go is an open source project. + +It is the work of hundreds of contributors. We appreciate your help! + + +## Filing issues + +When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions: + +1. What version of Go are you using (`go version`)? +2. What operating system and processor architecture are you using? +3. What did you do? +4. What did you expect to see? +5. What did you see instead? + +General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. +The gophers there will answer or ask you to file an issue if you've tripped over a bug. + +## Contributing code + +Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) +before sending patches. + +**We do not accept GitHub pull requests** +(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review). + +Unless otherwise noted, the Go source files are distributed under +the BSD-style license found in the LICENSE file. + diff --git a/vendor/golang.org/x/tools/CONTRIBUTORS b/vendor/golang.org/x/tools/CONTRIBUTORS new file mode 100644 index 0000000..1c4577e --- /dev/null +++ b/vendor/golang.org/x/tools/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/tools/LICENSE b/vendor/golang.org/x/tools/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/tools/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. 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 Google Inc. 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 +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. diff --git a/vendor/golang.org/x/tools/PATENTS b/vendor/golang.org/x/tools/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/tools/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/tools/README b/vendor/golang.org/x/tools/README new file mode 100644 index 0000000..d5944c6 --- /dev/null +++ b/vendor/golang.org/x/tools/README @@ -0,0 +1,10 @@ +This subrepository holds the source for various packages and tools that support +the Go programming language. + +Some of the tools, godoc and vet for example, are included in binary Go distributions. +Others, including the Go guru and the test coverage tool, can be fetched with "go get". + +Packages include a type-checker for Go and an implementation of the +Static Single Assignment form (SSA) representation for Go programs. + +To submit changes to this repository, see http://golang.org/doc/contribute.html. diff --git a/vendor/golang.org/x/tools/blog/atom/atom.go b/vendor/golang.org/x/tools/blog/atom/atom.go new file mode 100644 index 0000000..542c50e --- /dev/null +++ b/vendor/golang.org/x/tools/blog/atom/atom.go @@ -0,0 +1,61 @@ +// 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. + +// Adapted from encoding/xml/read_test.go. + +// Package atom defines XML data structures for an Atom feed. +package atom // import "golang.org/x/tools/blog/atom" + +import ( + "encoding/xml" + "time" +) + +type Feed struct { + XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"` + Title string `xml:"title"` + ID string `xml:"id"` + Link []Link `xml:"link"` + Updated TimeStr `xml:"updated"` + Author *Person `xml:"author"` + Entry []*Entry `xml:"entry"` +} + +type Entry struct { + Title string `xml:"title"` + ID string `xml:"id"` + Link []Link `xml:"link"` + Published TimeStr `xml:"published"` + Updated TimeStr `xml:"updated"` + Author *Person `xml:"author"` + Summary *Text `xml:"summary"` + Content *Text `xml:"content"` +} + +type Link struct { + Rel string `xml:"rel,attr,omitempty"` + Href string `xml:"href,attr"` + Type string `xml:"type,attr,omitempty"` + HrefLang string `xml:"hreflang,attr,omitempty"` + Title string `xml:"title,attr,omitempty"` + Length uint `xml:"length,attr,omitempty"` +} + +type Person struct { + Name string `xml:"name"` + URI string `xml:"uri,omitempty"` + Email string `xml:"email,omitempty"` + InnerXML string `xml:",innerxml"` +} + +type Text struct { + Type string `xml:"type,attr"` + Body string `xml:",chardata"` +} + +type TimeStr string + +func Time(t time.Time) TimeStr { + return TimeStr(t.Format("2006-01-02T15:04:05-07:00")) +} diff --git a/vendor/golang.org/x/tools/blog/blog.go b/vendor/golang.org/x/tools/blog/blog.go new file mode 100644 index 0000000..23c8dc6 --- /dev/null +++ b/vendor/golang.org/x/tools/blog/blog.go @@ -0,0 +1,424 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blog implements a web server for articles written in present format. +package blog // import "golang.org/x/tools/blog" + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" + "html/template" + "log" + "net/http" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + "time" + + "golang.org/x/tools/blog/atom" + "golang.org/x/tools/present" +) + +var validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`) + +// Config specifies Server configuration values. +type Config struct { + ContentPath string // Relative or absolute location of article files and related content. + TemplatePath string // Relative or absolute location of template files. + + BaseURL string // Absolute base URL (for permalinks; no trailing slash). + BasePath string // Base URL path relative to server root (no trailing slash). + GodocURL string // The base URL of godoc (for menu bar; no trailing slash). + Hostname string // Server host name, used for rendering ATOM feeds. + + HomeArticles int // Articles to display on the home page. + FeedArticles int // Articles to include in Atom and JSON feeds. + FeedTitle string // The title of the Atom XML feed + + PlayEnabled bool +} + +// Doc represents an article adorned with presentation data. +type Doc struct { + *present.Doc + Permalink string // Canonical URL for this document. + Path string // Path relative to server root (including base). + HTML template.HTML // rendered article + + Related []*Doc + Newer, Older *Doc +} + +// Server implements an http.Handler that serves blog articles. +type Server struct { + cfg Config + docs []*Doc + tags []string + docPaths map[string]*Doc // key is path without BasePath. + docTags map[string][]*Doc + template struct { + home, index, article, doc *template.Template + } + atomFeed []byte // pre-rendered Atom feed + jsonFeed []byte // pre-rendered JSON feed + content http.Handler +} + +// NewServer constructs a new Server using the specified config. +func NewServer(cfg Config) (*Server, error) { + present.PlayEnabled = cfg.PlayEnabled + + root := filepath.Join(cfg.TemplatePath, "root.tmpl") + parse := func(name string) (*template.Template, error) { + t := template.New("").Funcs(funcMap) + return t.ParseFiles(root, filepath.Join(cfg.TemplatePath, name)) + } + + s := &Server{cfg: cfg} + + // Parse templates. + var err error + s.template.home, err = parse("home.tmpl") + if err != nil { + return nil, err + } + s.template.index, err = parse("index.tmpl") + if err != nil { + return nil, err + } + s.template.article, err = parse("article.tmpl") + if err != nil { + return nil, err + } + p := present.Template().Funcs(funcMap) + s.template.doc, err = p.ParseFiles(filepath.Join(cfg.TemplatePath, "doc.tmpl")) + if err != nil { + return nil, err + } + + // Load content. + err = s.loadDocs(filepath.Clean(cfg.ContentPath)) + if err != nil { + return nil, err + } + + err = s.renderAtomFeed() + if err != nil { + return nil, err + } + + err = s.renderJSONFeed() + if err != nil { + return nil, err + } + + // Set up content file server. + s.content = http.StripPrefix(s.cfg.BasePath, http.FileServer(http.Dir(cfg.ContentPath))) + + return s, nil +} + +var funcMap = template.FuncMap{ + "sectioned": sectioned, + "authors": authors, +} + +// sectioned returns true if the provided Doc contains more than one section. +// This is used to control whether to display the table of contents and headings. +func sectioned(d *present.Doc) bool { + return len(d.Sections) > 1 +} + +// authors returns a comma-separated list of author names. +func authors(authors []present.Author) string { + var b bytes.Buffer + last := len(authors) - 1 + for i, a := range authors { + if i > 0 { + if i == last { + b.WriteString(" and ") + } else { + b.WriteString(", ") + } + } + b.WriteString(authorName(a)) + } + return b.String() +} + +// authorName returns the first line of the Author text: the author's name. +func authorName(a present.Author) string { + el := a.TextElem() + if len(el) == 0 { + return "" + } + text, ok := el[0].(present.Text) + if !ok || len(text.Lines) == 0 { + return "" + } + return text.Lines[0] +} + +// loadDocs reads all content from the provided file system root, renders all +// the articles it finds, adds them to the Server's docs field, computes the +// denormalized docPaths, docTags, and tags fields, and populates the various +// helper fields (Next, Previous, Related) for each Doc. +func (s *Server) loadDocs(root string) error { + // Read content into docs field. + const ext = ".article" + fn := func(p string, info os.FileInfo, err error) error { + if filepath.Ext(p) != ext { + return nil + } + f, err := os.Open(p) + if err != nil { + return err + } + defer f.Close() + d, err := present.Parse(f, p, 0) + if err != nil { + return err + } + html := new(bytes.Buffer) + err = d.Render(html, s.template.doc) + if err != nil { + return err + } + p = p[len(root) : len(p)-len(ext)] // trim root and extension + p = filepath.ToSlash(p) + s.docs = append(s.docs, &Doc{ + Doc: d, + Path: s.cfg.BasePath + p, + Permalink: s.cfg.BaseURL + p, + HTML: template.HTML(html.String()), + }) + return nil + } + err := filepath.Walk(root, fn) + if err != nil { + return err + } + sort.Sort(docsByTime(s.docs)) + + // Pull out doc paths and tags and put in reverse-associating maps. + s.docPaths = make(map[string]*Doc) + s.docTags = make(map[string][]*Doc) + for _, d := range s.docs { + s.docPaths[strings.TrimPrefix(d.Path, s.cfg.BasePath)] = d + for _, t := range d.Tags { + s.docTags[t] = append(s.docTags[t], d) + } + } + + // Pull out unique sorted list of tags. + for t := range s.docTags { + s.tags = append(s.tags, t) + } + sort.Strings(s.tags) + + // Set up presentation-related fields, Newer, Older, and Related. + for _, doc := range s.docs { + // Newer, Older: docs adjacent to doc + for i := range s.docs { + if s.docs[i] != doc { + continue + } + if i > 0 { + doc.Newer = s.docs[i-1] + } + if i+1 < len(s.docs) { + doc.Older = s.docs[i+1] + } + break + } + + // Related: all docs that share tags with doc. + related := make(map[*Doc]bool) + for _, t := range doc.Tags { + for _, d := range s.docTags[t] { + if d != doc { + related[d] = true + } + } + } + for d := range related { + doc.Related = append(doc.Related, d) + } + sort.Sort(docsByTime(doc.Related)) + } + + return nil +} + +// renderAtomFeed generates an XML Atom feed and stores it in the Server's +// atomFeed field. +func (s *Server) renderAtomFeed() error { + var updated time.Time + if len(s.docs) > 0 { + updated = s.docs[0].Time + } + feed := atom.Feed{ + Title: s.cfg.FeedTitle, + ID: "tag:" + s.cfg.Hostname + ",2013:" + s.cfg.Hostname, + Updated: atom.Time(updated), + Link: []atom.Link{{ + Rel: "self", + Href: s.cfg.BaseURL + "/feed.atom", + }}, + } + for i, doc := range s.docs { + if i >= s.cfg.FeedArticles { + break + } + e := &atom.Entry{ + Title: doc.Title, + ID: feed.ID + doc.Path, + Link: []atom.Link{{ + Rel: "alternate", + Href: doc.Permalink, + }}, + Published: atom.Time(doc.Time), + Updated: atom.Time(doc.Time), + Summary: &atom.Text{ + Type: "html", + Body: summary(doc), + }, + Content: &atom.Text{ + Type: "html", + Body: string(doc.HTML), + }, + Author: &atom.Person{ + Name: authors(doc.Authors), + }, + } + feed.Entry = append(feed.Entry, e) + } + data, err := xml.Marshal(&feed) + if err != nil { + return err + } + s.atomFeed = data + return nil +} + +type jsonItem struct { + Title string + Link string + Time time.Time + Summary string + Content string + Author string +} + +// renderJSONFeed generates a JSON feed and stores it in the Server's jsonFeed +// field. +func (s *Server) renderJSONFeed() error { + var feed []jsonItem + for i, doc := range s.docs { + if i >= s.cfg.FeedArticles { + break + } + item := jsonItem{ + Title: doc.Title, + Link: doc.Permalink, + Time: doc.Time, + Summary: summary(doc), + Content: string(doc.HTML), + Author: authors(doc.Authors), + } + feed = append(feed, item) + } + data, err := json.Marshal(feed) + if err != nil { + return err + } + s.jsonFeed = data + return nil +} + +// summary returns the first paragraph of text from the provided Doc. +func summary(d *Doc) string { + if len(d.Sections) == 0 { + return "" + } + for _, elem := range d.Sections[0].Elem { + text, ok := elem.(present.Text) + if !ok || text.Pre { + // skip everything but non-text elements + continue + } + var buf bytes.Buffer + for _, s := range text.Lines { + buf.WriteString(string(present.Style(s))) + buf.WriteByte('\n') + } + return buf.String() + } + return "" +} + +// rootData encapsulates data destined for the root template. +type rootData struct { + Doc *Doc + BasePath string + GodocURL string + Data interface{} +} + +// ServeHTTP serves the front, index, and article pages +// as well as the ATOM and JSON feeds. +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var ( + d = rootData{BasePath: s.cfg.BasePath, GodocURL: s.cfg.GodocURL} + t *template.Template + ) + switch p := strings.TrimPrefix(r.URL.Path, s.cfg.BasePath); p { + case "/": + d.Data = s.docs + if len(s.docs) > s.cfg.HomeArticles { + d.Data = s.docs[:s.cfg.HomeArticles] + } + t = s.template.home + case "/index": + d.Data = s.docs + t = s.template.index + case "/feed.atom", "/feeds/posts/default": + w.Header().Set("Content-type", "application/atom+xml; charset=utf-8") + w.Write(s.atomFeed) + return + case "/.json": + if p := r.FormValue("jsonp"); validJSONPFunc.MatchString(p) { + w.Header().Set("Content-type", "application/javascript; charset=utf-8") + fmt.Fprintf(w, "%v(%s)", p, s.jsonFeed) + return + } + w.Header().Set("Content-type", "application/json; charset=utf-8") + w.Write(s.jsonFeed) + return + default: + doc, ok := s.docPaths[p] + if !ok { + // Not a doc; try to just serve static content. + s.content.ServeHTTP(w, r) + return + } + d.Doc = doc + t = s.template.article + } + err := t.ExecuteTemplate(w, "root", d) + if err != nil { + log.Println(err) + } +} + +// docsByTime implements sort.Interface, sorting Docs by their Time field. +type docsByTime []*Doc + +func (s docsByTime) Len() int { return len(s) } +func (s docsByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) } diff --git a/vendor/golang.org/x/tools/codereview.cfg b/vendor/golang.org/x/tools/codereview.cfg new file mode 100644 index 0000000..3f8b14b --- /dev/null +++ b/vendor/golang.org/x/tools/codereview.cfg @@ -0,0 +1 @@ +issuerepo: golang/go diff --git a/vendor/golang.org/x/tools/present/args.go b/vendor/golang.org/x/tools/present/args.go new file mode 100644 index 0000000..49ee1a9 --- /dev/null +++ b/vendor/golang.org/x/tools/present/args.go @@ -0,0 +1,229 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package present + +import ( + "errors" + "regexp" + "strconv" + "unicode/utf8" +) + +// This file is stolen from go/src/cmd/godoc/codewalk.go. +// It's an evaluator for the file address syntax implemented by acme and sam, +// but using Go-native regular expressions. +// To keep things reasonably close, this version uses (?m:re) for all user-provided +// regular expressions. That is the only change to the code from codewalk.go. +// See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II +// for details on the syntax. + +// addrToByte evaluates the given address starting at offset start in data. +// It returns the lo and hi byte offset of the matched region within data. +func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err error) { + if addr == "" { + lo, hi = start, len(data) + return + } + var ( + dir byte + prevc byte + charOffset bool + ) + lo = start + hi = start + for addr != "" && err == nil { + c := addr[0] + switch c { + default: + err = errors.New("invalid address syntax near " + string(c)) + case ',': + if len(addr) == 1 { + hi = len(data) + } else { + _, hi, err = addrToByteRange(addr[1:], hi, data) + } + return + + case '+', '-': + if prevc == '+' || prevc == '-' { + lo, hi, err = addrNumber(data, lo, hi, prevc, 1, charOffset) + } + dir = c + + case '$': + lo = len(data) + hi = len(data) + if len(addr) > 1 { + dir = '+' + } + + case '#': + charOffset = true + + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + var i int + for i = 1; i < len(addr); i++ { + if addr[i] < '0' || addr[i] > '9' { + break + } + } + var n int + n, err = strconv.Atoi(addr[0:i]) + if err != nil { + break + } + lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffset) + dir = 0 + charOffset = false + prevc = c + addr = addr[i:] + continue + + case '/': + var i, j int + Regexp: + for i = 1; i < len(addr); i++ { + switch addr[i] { + case '\\': + i++ + case '/': + j = i + 1 + break Regexp + } + } + if j == 0 { + j = i + } + pattern := addr[1:i] + lo, hi, err = addrRegexp(data, lo, hi, dir, pattern) + prevc = c + addr = addr[j:] + continue + } + prevc = c + addr = addr[1:] + } + + if err == nil && dir != 0 { + lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset) + } + if err != nil { + return 0, 0, err + } + return lo, hi, nil +} + +// addrNumber applies the given dir, n, and charOffset to the address lo, hi. +// dir is '+' or '-', n is the count, and charOffset is true if the syntax +// used was #n. Applying +n (or +#n) means to advance n lines +// (or characters) after hi. Applying -n (or -#n) means to back up n lines +// (or characters) before lo. +// The return value is the new lo, hi. +func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, error) { + switch dir { + case 0: + lo = 0 + hi = 0 + fallthrough + + case '+': + if charOffset { + pos := hi + for ; n > 0 && pos < len(data); n-- { + _, size := utf8.DecodeRune(data[pos:]) + pos += size + } + if n == 0 { + return pos, pos, nil + } + break + } + // find next beginning of line + if hi > 0 { + for hi < len(data) && data[hi-1] != '\n' { + hi++ + } + } + lo = hi + if n == 0 { + return lo, hi, nil + } + for ; hi < len(data); hi++ { + if data[hi] != '\n' { + continue + } + switch n--; n { + case 1: + lo = hi + 1 + case 0: + return lo, hi + 1, nil + } + } + + case '-': + if charOffset { + // Scan backward for bytes that are not UTF-8 continuation bytes. + pos := lo + for ; pos > 0 && n > 0; pos-- { + if data[pos]&0xc0 != 0x80 { + n-- + } + } + if n == 0 { + return pos, pos, nil + } + break + } + // find earlier beginning of line + for lo > 0 && data[lo-1] != '\n' { + lo-- + } + hi = lo + if n == 0 { + return lo, hi, nil + } + for ; lo >= 0; lo-- { + if lo > 0 && data[lo-1] != '\n' { + continue + } + switch n--; n { + case 1: + hi = lo + case 0: + return lo, hi, nil + } + } + } + + return 0, 0, errors.New("address out of range") +} + +// addrRegexp searches for pattern in the given direction starting at lo, hi. +// The direction dir is '+' (search forward from hi) or '-' (search backward from lo). +// Backward searches are unimplemented. +func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, error) { + // We want ^ and $ to work as in sam/acme, so use ?m. + re, err := regexp.Compile("(?m:" + pattern + ")") + if err != nil { + return 0, 0, err + } + if dir == '-' { + // Could implement reverse search using binary search + // through file, but that seems like overkill. + return 0, 0, errors.New("reverse search not implemented") + } + m := re.FindIndex(data[hi:]) + if len(m) > 0 { + m[0] += hi + m[1] += hi + } else if hi > 0 { + // No match. Wrap to beginning of data. + m = re.FindIndex(data) + } + if len(m) == 0 { + return 0, 0, errors.New("no match for " + pattern) + } + return m[0], m[1], nil +} diff --git a/vendor/golang.org/x/tools/present/caption.go b/vendor/golang.org/x/tools/present/caption.go new file mode 100644 index 0000000..00e0b5d --- /dev/null +++ b/vendor/golang.org/x/tools/present/caption.go @@ -0,0 +1,22 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package present + +import "strings" + +func init() { + Register("caption", parseCaption) +} + +type Caption struct { + Text string +} + +func (c Caption) TemplateName() string { return "caption" } + +func parseCaption(_ *Context, _ string, _ int, text string) (Elem, error) { + text = strings.TrimSpace(strings.TrimPrefix(text, ".caption")) + return Caption{text}, nil +} diff --git a/vendor/golang.org/x/tools/present/code.go b/vendor/golang.org/x/tools/present/code.go new file mode 100644 index 0000000..b47a72a --- /dev/null +++ b/vendor/golang.org/x/tools/present/code.go @@ -0,0 +1,267 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package present + +import ( + "bufio" + "bytes" + "fmt" + "html/template" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +// PlayEnabled specifies whether runnable playground snippets should be +// displayed in the present user interface. +var PlayEnabled = false + +// TODO(adg): replace the PlayEnabled flag with something less spaghetti-like. +// Instead this will probably be determined by a template execution Context +// value that contains various global metadata required when rendering +// templates. + +// NotesEnabled specifies whether presenter notes should be displayed in the +// present user interface. +var NotesEnabled = false + +func init() { + Register("code", parseCode) + Register("play", parseCode) +} + +type Code struct { + Text template.HTML + Play bool // runnable code + Edit bool // editable code + FileName string // file name + Ext string // file extension + Raw []byte // content of the file +} + +func (c Code) TemplateName() string { return "code" } + +// The input line is a .code or .play entry with a file name and an optional HLfoo marker on the end. +// Anything between the file and HL (if any) is an address expression, which we treat as a string here. +// We pick off the HL first, for easy parsing. +var ( + highlightRE = regexp.MustCompile(`\s+HL([a-zA-Z0-9_]+)?$`) + hlCommentRE = regexp.MustCompile(`(.+) // HL(.*)$`) + codeRE = regexp.MustCompile(`\.(code|play)\s+((?:(?:-edit|-numbers)\s+)*)([^\s]+)(?:\s+(.*))?$`) +) + +// parseCode parses a code present directive. Its syntax: +// .code [-numbers] [-edit] [address] [highlight] +// The directive may also be ".play" if the snippet is executable. +func parseCode(ctx *Context, sourceFile string, sourceLine int, cmd string) (Elem, error) { + cmd = strings.TrimSpace(cmd) + + // Pull off the HL, if any, from the end of the input line. + highlight := "" + if hl := highlightRE.FindStringSubmatchIndex(cmd); len(hl) == 4 { + if hl[2] < 0 || hl[3] < 0 { + return nil, fmt.Errorf("%s:%d invalid highlight syntax", sourceFile, sourceLine) + } + highlight = cmd[hl[2]:hl[3]] + cmd = cmd[:hl[2]-2] + } + + // Parse the remaining command line. + // Arguments: + // args[0]: whole match + // args[1]: .code/.play + // args[2]: flags ("-edit -numbers") + // args[3]: file name + // args[4]: optional address + args := codeRE.FindStringSubmatch(cmd) + if len(args) != 5 { + return nil, fmt.Errorf("%s:%d: syntax error for .code/.play invocation", sourceFile, sourceLine) + } + command, flags, file, addr := args[1], args[2], args[3], strings.TrimSpace(args[4]) + play := command == "play" && PlayEnabled + + // Read in code file and (optionally) match address. + filename := filepath.Join(filepath.Dir(sourceFile), file) + textBytes, err := ctx.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("%s:%d: %v", sourceFile, sourceLine, err) + } + lo, hi, err := addrToByteRange(addr, 0, textBytes) + if err != nil { + return nil, fmt.Errorf("%s:%d: %v", sourceFile, sourceLine, err) + } + if lo > hi { + // The search in addrToByteRange can wrap around so we might + // end up with the range ending before its starting point + hi, lo = lo, hi + } + + // Acme pattern matches can stop mid-line, + // so run to end of line in both directions if not at line start/end. + for lo > 0 && textBytes[lo-1] != '\n' { + lo-- + } + if hi > 0 { + for hi < len(textBytes) && textBytes[hi-1] != '\n' { + hi++ + } + } + + lines := codeLines(textBytes, lo, hi) + + data := &codeTemplateData{ + Lines: formatLines(lines, highlight), + Edit: strings.Contains(flags, "-edit"), + Numbers: strings.Contains(flags, "-numbers"), + } + + // Include before and after in a hidden span for playground code. + if play { + data.Prefix = textBytes[:lo] + data.Suffix = textBytes[hi:] + } + + var buf bytes.Buffer + if err := codeTemplate.Execute(&buf, data); err != nil { + return nil, err + } + return Code{ + Text: template.HTML(buf.String()), + Play: play, + Edit: data.Edit, + FileName: filepath.Base(filename), + Ext: filepath.Ext(filename), + Raw: rawCode(lines), + }, nil +} + +// formatLines returns a new slice of codeLine with the given lines +// replacing tabs with spaces and adding highlighting where needed. +func formatLines(lines []codeLine, highlight string) []codeLine { + formatted := make([]codeLine, len(lines)) + for i, line := range lines { + // Replace tabs with spaces, which work better in HTML. + line.L = strings.Replace(line.L, "\t", " ", -1) + + // Highlight lines that end with "// HL[highlight]" + // and strip the magic comment. + if m := hlCommentRE.FindStringSubmatch(line.L); m != nil { + line.L = m[1] + line.HL = m[2] == highlight + } + + formatted[i] = line + } + return formatted +} + +// rawCode returns the code represented by the given codeLines without any kind +// of formatting. +func rawCode(lines []codeLine) []byte { + b := new(bytes.Buffer) + for _, line := range lines { + b.WriteString(line.L) + b.WriteByte('\n') + } + return b.Bytes() +} + +type codeTemplateData struct { + Lines []codeLine + Prefix, Suffix []byte + Edit, Numbers bool +} + +var leadingSpaceRE = regexp.MustCompile(`^[ \t]*`) + +var codeTemplate = template.Must(template.New("code").Funcs(template.FuncMap{ + "trimSpace": strings.TrimSpace, + "leadingSpace": leadingSpaceRE.FindString, +}).Parse(codeTemplateHTML)) + +const codeTemplateHTML = ` +{{with .Prefix}}
{{printf "%s" .}}
{{end}} + +{{/* + */}}{{range .Lines}}{{/* + */}}{{if .HL}}{{leadingSpace .L}}{{trimSpace .L}}{{/* + */}}{{else}}{{.L}}{{end}}{{/* +*/}} +{{end}} + +{{with .Suffix}}
{{printf "%s" .}}
{{end}} +` + +// codeLine represents a line of code extracted from a source file. +type codeLine struct { + L string // The line of code. + N int // The line number from the source file. + HL bool // Whether the line should be highlighted. +} + +// codeLines takes a source file and returns the lines that +// span the byte range specified by start and end. +// It discards lines that end in "OMIT". +func codeLines(src []byte, start, end int) (lines []codeLine) { + startLine := 1 + for i, b := range src { + if i == start { + break + } + if b == '\n' { + startLine++ + } + } + s := bufio.NewScanner(bytes.NewReader(src[start:end])) + for n := startLine; s.Scan(); n++ { + l := s.Text() + if strings.HasSuffix(l, "OMIT") { + continue + } + lines = append(lines, codeLine{L: l, N: n}) + } + // Trim leading and trailing blank lines. + for len(lines) > 0 && len(lines[0].L) == 0 { + lines = lines[1:] + } + for len(lines) > 0 && len(lines[len(lines)-1].L) == 0 { + lines = lines[:len(lines)-1] + } + return +} + +func parseArgs(name string, line int, args []string) (res []interface{}, err error) { + res = make([]interface{}, len(args)) + for i, v := range args { + if len(v) == 0 { + return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v) + } + switch v[0] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + n, err := strconv.Atoi(v) + if err != nil { + return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v) + } + res[i] = n + case '/': + if len(v) < 2 || v[len(v)-1] != '/' { + return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v) + } + res[i] = v + case '$': + res[i] = "$" + case '_': + if len(v) == 1 { + // Do nothing; "_" indicates an intentionally empty parameter. + break + } + fallthrough + default: + return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v) + } + } + return +} diff --git a/vendor/golang.org/x/tools/present/code_test.go b/vendor/golang.org/x/tools/present/code_test.go new file mode 100644 index 0000000..b01a4e3 --- /dev/null +++ b/vendor/golang.org/x/tools/present/code_test.go @@ -0,0 +1,225 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package present + +import ( + "fmt" + "html/template" + "strings" + "testing" +) + +func TestParseCode(t *testing.T) { + // Enable play but revert the change at the end. + defer func(play bool) { PlayEnabled = play }(PlayEnabled) + PlayEnabled = true + + helloTest := []byte(` +package main + +import "fmt" + +func main() { + fmt.Println("hello, test") +} +`) + helloTestHTML := template.HTML(` +
package main
+
+import "fmt"
+
+func main() {
+    fmt.Println("hello, test")
+}
+
+`) + helloTestHL := []byte(` +package main + +import "fmt" // HLimport + +func main() { // HLfunc + fmt.Println("hello, test") // HL +} +`) + highlight := func(h template.HTML, s string) template.HTML { + return template.HTML(strings.Replace(string(h), s, ""+s+"", -1)) + } + read := func(b []byte, err error) func(string) ([]byte, error) { + return func(string) ([]byte, error) { return b, err } + } + + tests := []struct { + name string + readFile func(string) ([]byte, error) + sourceFile string + cmd string + err string + Code + }{ + { + name: "all code, no play", + readFile: read(helloTest, nil), + sourceFile: "main.go", + cmd: ".code main.go", + Code: Code{ + Ext: ".go", + FileName: "main.go", + Raw: helloTest, + Text: helloTestHTML, + }, + }, + { + name: "all code, play", + readFile: read(helloTest, nil), + sourceFile: "main.go", + cmd: ".play main.go", + Code: Code{ + Ext: ".go", + FileName: "main.go", + Play: true, + Raw: helloTest, + Text: helloTestHTML, + }, + }, + { + name: "all code, highlighted", + readFile: read(helloTestHL, nil), + sourceFile: "main.go", + cmd: ".code main.go", + Code: Code{ + Ext: ".go", + FileName: "main.go", + Raw: helloTestHL, + Text: highlight(helloTestHTML, "fmt.Println("hello, test")"), + }, + }, + { + name: "highlight only func", + readFile: read(helloTestHL, nil), + sourceFile: "main.go", + cmd: ".code main.go HLfunc", + Code: Code{ + Ext: ".go", + FileName: "main.go", + Play: false, + Raw: []byte("package main\n\nimport \"fmt\" // HLimport\n\nfunc main() { // HLfunc\n\tfmt.Println(\"hello, test\") // HL\n}"), + Text: highlight(helloTestHTML, "func main() {"), + }, + }, + { + name: "bad highlight syntax", + readFile: read(helloTest, nil), + sourceFile: "main.go", + cmd: ".code main.go HL", + err: "invalid highlight syntax", + }, + { + name: "error reading file", + readFile: read(nil, fmt.Errorf("nope")), + sourceFile: "main.go", + cmd: ".code main.go", + err: "main.go:0: nope", + }, + { + name: "from func main to the end", + readFile: read(helloTest, nil), + sourceFile: "main.go", + cmd: ".code main.go /func main/,", + Code: Code{ + Ext: ".go", + FileName: "main.go", + Play: false, + Raw: []byte("func main() {\n\tfmt.Println(\"hello, test\")\n}"), + Text: "
func main() {\n    fmt.Println("hello, test")\n}\n
", + }, + }, + { + name: "just func main", + readFile: read(helloTest, nil), + sourceFile: "main.go", + cmd: ".code main.go /func main/", + Code: Code{ + Ext: ".go", + FileName: "main.go", + Play: false, + Raw: []byte("func main() {"), + Text: "
func main() {\n
", + }, + }, + { + name: "bad address", + readFile: read(helloTest, nil), + sourceFile: "main.go", + cmd: ".code main.go /function main/", + err: "main.go:0: no match for function main", + }, + { + name: "all code with numbers", + readFile: read(helloTest, nil), + sourceFile: "main.go", + cmd: ".code -numbers main.go", + Code: Code{ + Ext: ".go", + FileName: "main.go", + Raw: helloTest, + // Replacing the first "
"
+				Text: "
" + helloTestHTML[6:],
+			},
+		},
+		{
+			name:       "all code editable",
+			readFile:   read(helloTest, nil),
+			sourceFile: "main.go",
+			cmd:        ".code -edit main.go",
+			Code: Code{
+				Ext:      ".go",
+				FileName: "main.go",
+				Raw:      helloTest,
+				Text:     "
" + helloTestHTML[6:],
+			},
+		},
+	}
+
+	trimHTML := func(t template.HTML) string { return strings.TrimSpace(string(t)) }
+	trimBytes := func(b []byte) string { return strings.TrimSpace(string(b)) }
+
+	for _, tt := range tests {
+		ctx := &Context{tt.readFile}
+		e, err := parseCode(ctx, tt.sourceFile, 0, tt.cmd)
+		if err != nil {
+			if tt.err == "" {
+				t.Errorf("%s: unexpected error %v", tt.name, err)
+			} else if !strings.Contains(err.Error(), tt.err) {
+				t.Errorf("%s: expected error %s; got %v", tt.name, tt.err, err)
+			}
+			continue
+		}
+		if tt.err != "" {
+			t.Errorf("%s: expected error %s; but got none", tt.name, tt.err)
+			continue
+		}
+		c, ok := e.(Code)
+		if !ok {
+			t.Errorf("%s: expected a Code value; got %T", tt.name, e)
+			continue
+		}
+		if c.FileName != tt.FileName {
+			t.Errorf("%s: expected FileName %s; got %s", tt.name, tt.FileName, c.FileName)
+		}
+		if c.Ext != tt.Ext {
+			t.Errorf("%s: expected Ext %s; got %s", tt.name, tt.Ext, c.Ext)
+		}
+		if c.Play != tt.Play {
+			t.Errorf("%s: expected Play %v; got %v", tt.name, tt.Play, c.Play)
+		}
+		if got, wants := trimBytes(c.Raw), trimBytes(tt.Raw); got != wants {
+			t.Errorf("%s: expected Raw \n%q\n; got \n%q\n", tt.name, wants, got)
+		}
+		if got, wants := trimHTML(c.Text), trimHTML(tt.Text); got != wants {
+			t.Errorf("%s: expected Text \n%q\n; got \n%q\n", tt.name, wants, got)
+		}
+	}
+}
diff --git a/vendor/golang.org/x/tools/present/doc.go b/vendor/golang.org/x/tools/present/doc.go
new file mode 100644
index 0000000..804ec84
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/doc.go
@@ -0,0 +1,266 @@
+// Copyright 2011 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.
+
+/*
+The present file format
+
+Present files have the following format.  The first non-blank non-comment
+line is the title, so the header looks like
+
+	Title of document
+	Subtitle of document
+	15:04 2 Jan 2006
+	Tags: foo, bar, baz
+	
+	Author Name
+	Job title, Company
+	joe@example.com
+	http://url/
+	@twitter_name
+
+The subtitle, date, and tags lines are optional.
+
+The date line may be written without a time:
+	2 Jan 2006
+In this case, the time will be interpreted as 10am UTC on that date.
+
+The tags line is a comma-separated list of tags that may be used to categorize
+the document.
+
+The author section may contain a mixture of text, twitter names, and links.
+For slide presentations, only the plain text lines will be displayed on the
+first slide.
+
+Multiple presenters may be specified, separated by a blank line.
+
+After that come slides/sections, each after a blank line:
+
+	* Title of slide or section (must have asterisk)
+
+	Some Text
+
+	** Subsection
+
+	- bullets
+	- more bullets
+	- a bullet with
+
+	*** Sub-subsection
+
+	Some More text
+
+	  Preformatted text
+	  is indented (however you like)
+
+	Further Text, including invocations like:
+
+	.code x.go /^func main/,/^}/
+	.play y.go
+	.image image.jpg
+	.background image.jpg
+	.iframe http://foo
+	.link http://foo label
+	.html file.html
+	.caption _Gopher_ by [[http://www.reneefrench.com][Renée French]]
+
+	Again, more text
+
+Blank lines are OK (not mandatory) after the title and after the
+text.  Text, bullets, and .code etc. are all optional; title is
+not.
+
+Lines starting with # in column 1 are commentary.
+
+Fonts:
+
+Within the input for plain text or lists, text bracketed by font
+markers will be presented in italic, bold, or program font.
+Marker characters are _ (italic), * (bold) and ` (program font).
+An opening marker must be preceded by a space or punctuation
+character or else be at start of a line; similarly, a closing
+marker must be followed by a space or punctuation character or
+else be at the end of a line. Unmatched markers appear as plain text.
+There must be no spaces between markers. Within marked text,
+a single marker character becomes a space and a doubled single
+marker quotes the marker character.
+
+at the beginning of a line or
+else be preceded by a space or punctuation; similarly a closing
+marker must be at the end of the lineo
+
+	_italic_
+	*bold*
+	`program`
+	Markup—_especially_italic_text_—can easily be overused.
+	_Why_use_scoped__ptr_? Use plain ***ptr* instead.
+
+Inline links:
+
+Links can be included in any text with the form [[url][label]], or
+[[url]] to use the URL itself as the label.
+
+Functions:
+
+A number of template functions are available through invocations
+in the input text. Each such invocation contains a period as the
+first character on the line, followed immediately by the name of
+the function, followed by any arguments. A typical invocation might
+be
+	.play demo.go /^func show/,/^}/
+(except that the ".play" must be at the beginning of the line and
+not be indented like this.)
+
+Here follows a description of the functions:
+
+code:
+
+Injects program source into the output by extracting code from files
+and injecting them as HTML-escaped 
 blocks.  The argument is
+a file name followed by an optional address that specifies what
+section of the file to display. The address syntax is similar in
+its simplest form to that of ed, but comes from sam and is more
+general. See
+	http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II
+for full details. The displayed block is always rounded out to a
+full line at both ends.
+
+If no pattern is present, the entire file is displayed.
+
+Any line in the program that ends with the four characters
+	OMIT
+is deleted from the source before inclusion, making it easy
+to write things like
+	.code test.go /START OMIT/,/END OMIT/
+to find snippets like this
+	tedious_code = boring_function()
+	// START OMIT
+	interesting_code = fascinating_function()
+	// END OMIT
+and see only this:
+	interesting_code = fascinating_function()
+
+Also, inside the displayed text a line that ends
+	// HL
+will be highlighted in the display; the 'h' key in the browser will
+toggle extra emphasis of any highlighted lines. A highlighting mark
+may have a suffix word, such as
+	// HLxxx
+Such highlights are enabled only if the code invocation ends with
+"HL" followed by the word:
+	.code test.go /^type Foo/,/^}/ HLxxx
+
+The .code function may take one or more flags immediately preceding
+the filename. This command shows test.go in an editable text area:
+	.code -edit test.go
+This command shows test.go with line numbers:
+	.code -numbers test.go
+
+play:
+
+The function "play" is the same as "code" but puts a button
+on the displayed source so the program can be run from the browser.
+Although only the selected text is shown, all the source is included
+in the HTML output so it can be presented to the compiler.
+
+link:
+
+Create a hyperlink. The syntax is 1 or 2 space-separated arguments.
+The first argument is always the HTTP URL.  If there is a second
+argument, it is the text label to display for this link.
+
+	.link http://golang.org golang.org
+
+image:
+
+The template uses the function "image" to inject picture files.
+
+The syntax is simple: 1 or 3 space-separated arguments.
+The first argument is always the file name.
+If there are more arguments, they are the height and width;
+both must be present, or substituted with an underscore.
+Replacing a dimension argument with the underscore parameter
+preserves the aspect ratio of the image when scaling.
+
+	.image images/betsy.jpg 100 200
+
+	.image images/janet.jpg _ 300
+
+video:
+
+The template uses the function "video" to inject video files.
+
+The syntax is simple: 2 or 4 space-separated arguments.
+The first argument is always the file name.
+The second argument is always the file content-type.
+If there are more arguments, they are the height and width;
+both must be present, or substituted with an underscore.
+Replacing a dimension argument with the underscore parameter
+preserves the aspect ratio of the video when scaling.
+
+	.video videos/evangeline.mp4 video/mp4 400 600
+
+	.video videos/mabel.ogg video/ogg 500 _
+
+background:
+
+The template uses the function "background" to set the background image for
+a slide.  The only argument is the file name of the image.
+
+	.background images/susan.jpg
+
+caption:
+
+The template uses the function "caption" to inject figure captions.
+
+The text after ".caption" is embedded in a figcaption element after
+processing styling and links as in standard text lines.
+
+	.caption _Gopher_ by [[http://www.reneefrench.com][Renée French]]
+
+iframe:
+
+The function "iframe" injects iframes (pages inside pages).
+Its syntax is the same as that of image.
+
+html:
+
+The function html includes the contents of the specified file as
+unescaped HTML. This is useful for including custom HTML elements
+that cannot be created using only the slide format.
+It is your responsibilty to make sure the included HTML is valid and safe.
+
+	.html file.html
+
+Presenter notes:
+
+Presenter notes may be enabled by appending the "-notes" flag when you run
+your "present" binary.
+
+This will allow you to open a second window by pressing 'N' from your browser
+displaying your slides. The second window is completely synced with your main
+window, except that presenter notes are only visible on the second window.
+
+Lines that begin with ": " are treated as presenter notes.
+
+	* Title of slide
+
+	Some Text
+
+	: Presenter notes (first paragraph)
+	: Presenter notes (subsequent paragraph(s))
+
+Notes may appear anywhere within the slide text. For example:
+
+	* Title of slide
+
+	: Presenter notes (first paragraph)
+
+	Some Text
+
+	: Presenter notes (subsequent paragraph(s))
+
+This has the same result as the example above.
+
+*/
+package present // import "golang.org/x/tools/present"
diff --git a/vendor/golang.org/x/tools/present/html.go b/vendor/golang.org/x/tools/present/html.go
new file mode 100644
index 0000000..cca90ef
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/html.go
@@ -0,0 +1,31 @@
+package present
+
+import (
+	"errors"
+	"html/template"
+	"path/filepath"
+	"strings"
+)
+
+func init() {
+	Register("html", parseHTML)
+}
+
+func parseHTML(ctx *Context, fileName string, lineno int, text string) (Elem, error) {
+	p := strings.Fields(text)
+	if len(p) != 2 {
+		return nil, errors.New("invalid .html args")
+	}
+	name := filepath.Join(filepath.Dir(fileName), p[1])
+	b, err := ctx.ReadFile(name)
+	if err != nil {
+		return nil, err
+	}
+	return HTML{template.HTML(b)}, nil
+}
+
+type HTML struct {
+	template.HTML
+}
+
+func (s HTML) TemplateName() string { return "html" }
diff --git a/vendor/golang.org/x/tools/present/iframe.go b/vendor/golang.org/x/tools/present/iframe.go
new file mode 100644
index 0000000..46649f0
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/iframe.go
@@ -0,0 +1,45 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import (
+	"fmt"
+	"strings"
+)
+
+func init() {
+	Register("iframe", parseIframe)
+}
+
+type Iframe struct {
+	URL    string
+	Width  int
+	Height int
+}
+
+func (i Iframe) TemplateName() string { return "iframe" }
+
+func parseIframe(ctx *Context, fileName string, lineno int, text string) (Elem, error) {
+	args := strings.Fields(text)
+	i := Iframe{URL: args[1]}
+	a, err := parseArgs(fileName, lineno, args[2:])
+	if err != nil {
+		return nil, err
+	}
+	switch len(a) {
+	case 0:
+		// no size parameters
+	case 2:
+		if v, ok := a[0].(int); ok {
+			i.Height = v
+		}
+		if v, ok := a[1].(int); ok {
+			i.Width = v
+		}
+	default:
+		return nil, fmt.Errorf("incorrect iframe invocation: %q", text)
+	}
+	return i, nil
+}
diff --git a/vendor/golang.org/x/tools/present/image.go b/vendor/golang.org/x/tools/present/image.go
new file mode 100644
index 0000000..cfa2af9
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/image.go
@@ -0,0 +1,50 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import (
+	"fmt"
+	"strings"
+)
+
+func init() {
+	Register("image", parseImage)
+}
+
+type Image struct {
+	URL    string
+	Width  int
+	Height int
+}
+
+func (i Image) TemplateName() string { return "image" }
+
+func parseImage(ctx *Context, fileName string, lineno int, text string) (Elem, error) {
+	args := strings.Fields(text)
+	img := Image{URL: args[1]}
+	a, err := parseArgs(fileName, lineno, args[2:])
+	if err != nil {
+		return nil, err
+	}
+	switch len(a) {
+	case 0:
+		// no size parameters
+	case 2:
+		// If a parameter is empty (underscore) or invalid
+		// leave the field set to zero. The "image" action
+		// template will then omit that img tag attribute and
+		// the browser will calculate the value to preserve
+		// the aspect ratio.
+		if v, ok := a[0].(int); ok {
+			img.Height = v
+		}
+		if v, ok := a[1].(int); ok {
+			img.Width = v
+		}
+	default:
+		return nil, fmt.Errorf("incorrect image invocation: %q", text)
+	}
+	return img, nil
+}
diff --git a/vendor/golang.org/x/tools/present/link.go b/vendor/golang.org/x/tools/present/link.go
new file mode 100644
index 0000000..2aead35
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/link.go
@@ -0,0 +1,100 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import (
+	"fmt"
+	"log"
+	"net/url"
+	"strings"
+)
+
+func init() {
+	Register("link", parseLink)
+}
+
+type Link struct {
+	URL   *url.URL
+	Label string
+}
+
+func (l Link) TemplateName() string { return "link" }
+
+func parseLink(ctx *Context, fileName string, lineno int, text string) (Elem, error) {
+	args := strings.Fields(text)
+	if len(args) < 2 {
+		return nil, fmt.Errorf("link element must have at least 2 arguments")
+	}
+	url, err := url.Parse(args[1])
+	if err != nil {
+		return nil, err
+	}
+	label := ""
+	if len(args) > 2 {
+		label = strings.Join(args[2:], " ")
+	} else {
+		scheme := url.Scheme + "://"
+		if url.Scheme == "mailto" {
+			scheme = "mailto:"
+		}
+		label = strings.Replace(url.String(), scheme, "", 1)
+	}
+	return Link{url, label}, nil
+}
+
+func renderLink(href, text string) string {
+	text = font(text)
+	if text == "" {
+		text = href
+	}
+	// Open links in new window only when their url is absolute.
+	target := "_blank"
+	if u, err := url.Parse(href); err != nil {
+		log.Println("renderLink parsing url:", err)
+	} else if !u.IsAbs() || u.Scheme == "javascript" {
+		target = "_self"
+	}
+
+	return fmt.Sprintf(`%s`, href, target, text)
+}
+
+// parseInlineLink parses an inline link at the start of s, and returns
+// a rendered HTML link and the total length of the raw inline link.
+// If no inline link is present, it returns all zeroes.
+func parseInlineLink(s string) (link string, length int) {
+	if !strings.HasPrefix(s, "[[") {
+		return
+	}
+	end := strings.Index(s, "]]")
+	if end == -1 {
+		return
+	}
+	urlEnd := strings.Index(s, "]")
+	rawURL := s[2:urlEnd]
+	const badURLChars = `<>"{}|\^[] ` + "`" // per RFC2396 section 2.4.3
+	if strings.ContainsAny(rawURL, badURLChars) {
+		return
+	}
+	if urlEnd == end {
+		simpleUrl := ""
+		url, err := url.Parse(rawURL)
+		if err == nil {
+			// If the URL is http://foo.com, drop the http://
+			// In other words, render [[http://golang.org]] as:
+			//   golang.org
+			if strings.HasPrefix(rawURL, url.Scheme+"://") {
+				simpleUrl = strings.TrimPrefix(rawURL, url.Scheme+"://")
+			} else if strings.HasPrefix(rawURL, url.Scheme+":") {
+				simpleUrl = strings.TrimPrefix(rawURL, url.Scheme+":")
+			}
+		}
+		return renderLink(rawURL, simpleUrl), end + 2
+	}
+	if s[urlEnd:urlEnd+2] != "][" {
+		return
+	}
+	text := s[urlEnd+2 : end]
+	return renderLink(rawURL, text), end + 2
+}
diff --git a/vendor/golang.org/x/tools/present/link_test.go b/vendor/golang.org/x/tools/present/link_test.go
new file mode 100644
index 0000000..334e72b
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/link_test.go
@@ -0,0 +1,40 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import "testing"
+
+func TestInlineParsing(t *testing.T) {
+	var tests = []struct {
+		in     string
+		link   string
+		text   string
+		length int
+	}{
+		{"[[http://golang.org]]", "http://golang.org", "golang.org", 21},
+		{"[[http://golang.org][]]", "http://golang.org", "http://golang.org", 23},
+		{"[[http://golang.org]] this is ignored", "http://golang.org", "golang.org", 21},
+		{"[[http://golang.org][link]]", "http://golang.org", "link", 27},
+		{"[[http://golang.org][two words]]", "http://golang.org", "two words", 32},
+		{"[[http://golang.org][*link*]]", "http://golang.org", "link", 29},
+		{"[[http://bad[url]]", "", "", 0},
+		{"[[http://golang.org][a [[link]] ]]", "http://golang.org", "a [[link", 31},
+		{"[[http:// *spaces* .com]]", "", "", 0},
+		{"[[http://bad`char.com]]", "", "", 0},
+		{" [[http://google.com]]", "", "", 0},
+		{"[[mailto:gopher@golang.org][Gopher]]", "mailto:gopher@golang.org", "Gopher", 36},
+		{"[[mailto:gopher@golang.org]]", "mailto:gopher@golang.org", "gopher@golang.org", 28},
+	}
+
+	for i, test := range tests {
+		link, length := parseInlineLink(test.in)
+		if length == 0 && test.length == 0 {
+			continue
+		}
+		if a := renderLink(test.link, test.text); length != test.length || link != a {
+			t.Errorf("#%d: parseInlineLink(%q):\ngot\t%q, %d\nwant\t%q, %d", i, test.in, link, length, a, test.length)
+		}
+	}
+}
diff --git a/vendor/golang.org/x/tools/present/parse.go b/vendor/golang.org/x/tools/present/parse.go
new file mode 100644
index 0000000..d7289db
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/parse.go
@@ -0,0 +1,559 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import (
+	"bufio"
+	"bytes"
+	"errors"
+	"fmt"
+	"html/template"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/url"
+	"regexp"
+	"strings"
+	"time"
+	"unicode"
+	"unicode/utf8"
+)
+
+var (
+	parsers = make(map[string]ParseFunc)
+	funcs   = template.FuncMap{}
+)
+
+// Template returns an empty template with the action functions in its FuncMap.
+func Template() *template.Template {
+	return template.New("").Funcs(funcs)
+}
+
+// Render renders the doc to the given writer using the provided template.
+func (d *Doc) Render(w io.Writer, t *template.Template) error {
+	data := struct {
+		*Doc
+		Template     *template.Template
+		PlayEnabled  bool
+		NotesEnabled bool
+	}{d, t, PlayEnabled, NotesEnabled}
+	return t.ExecuteTemplate(w, "root", data)
+}
+
+// Render renders the section to the given writer using the provided template.
+func (s *Section) Render(w io.Writer, t *template.Template) error {
+	data := struct {
+		*Section
+		Template    *template.Template
+		PlayEnabled bool
+	}{s, t, PlayEnabled}
+	return t.ExecuteTemplate(w, "section", data)
+}
+
+type ParseFunc func(ctx *Context, fileName string, lineNumber int, inputLine string) (Elem, error)
+
+// Register binds the named action, which does not begin with a period, to the
+// specified parser to be invoked when the name, with a period, appears in the
+// present input text.
+func Register(name string, parser ParseFunc) {
+	if len(name) == 0 || name[0] == ';' {
+		panic("bad name in Register: " + name)
+	}
+	parsers["."+name] = parser
+}
+
+// Doc represents an entire document.
+type Doc struct {
+	Title      string
+	Subtitle   string
+	Time       time.Time
+	Authors    []Author
+	TitleNotes []string
+	Sections   []Section
+	Tags       []string
+}
+
+// Author represents the person who wrote and/or is presenting the document.
+type Author struct {
+	Elem []Elem
+}
+
+// TextElem returns the first text elements of the author details.
+// This is used to display the author' name, job title, and company
+// without the contact details.
+func (p *Author) TextElem() (elems []Elem) {
+	for _, el := range p.Elem {
+		if _, ok := el.(Text); !ok {
+			break
+		}
+		elems = append(elems, el)
+	}
+	return
+}
+
+// Section represents a section of a document (such as a presentation slide)
+// comprising a title and a list of elements.
+type Section struct {
+	Number  []int
+	Title   string
+	Elem    []Elem
+	Notes   []string
+	Classes []string
+	Styles  []string
+}
+
+// HTMLAttributes for the section
+func (s Section) HTMLAttributes() template.HTMLAttr {
+	if len(s.Classes) == 0 && len(s.Styles) == 0 {
+		return ""
+	}
+
+	var class string
+	if len(s.Classes) > 0 {
+		class = fmt.Sprintf(`class=%q`, strings.Join(s.Classes, " "))
+	}
+	var style string
+	if len(s.Styles) > 0 {
+		style = fmt.Sprintf(`style=%q`, strings.Join(s.Styles, " "))
+	}
+	return template.HTMLAttr(strings.Join([]string{class, style}, " "))
+}
+
+// Sections contained within the section.
+func (s Section) Sections() (sections []Section) {
+	for _, e := range s.Elem {
+		if section, ok := e.(Section); ok {
+			sections = append(sections, section)
+		}
+	}
+	return
+}
+
+// Level returns the level of the given section.
+// The document title is level 1, main section 2, etc.
+func (s Section) Level() int {
+	return len(s.Number) + 1
+}
+
+// FormattedNumber returns a string containing the concatenation of the
+// numbers identifying a Section.
+func (s Section) FormattedNumber() string {
+	b := &bytes.Buffer{}
+	for _, n := range s.Number {
+		fmt.Fprintf(b, "%v.", n)
+	}
+	return b.String()
+}
+
+func (s Section) TemplateName() string { return "section" }
+
+// Elem defines the interface for a present element. That is, something that
+// can provide the name of the template used to render the element.
+type Elem interface {
+	TemplateName() string
+}
+
+// renderElem implements the elem template function, used to render
+// sub-templates.
+func renderElem(t *template.Template, e Elem) (template.HTML, error) {
+	var data interface{} = e
+	if s, ok := e.(Section); ok {
+		data = struct {
+			Section
+			Template *template.Template
+		}{s, t}
+	}
+	return execTemplate(t, e.TemplateName(), data)
+}
+
+func init() {
+	funcs["elem"] = renderElem
+}
+
+// execTemplate is a helper to execute a template and return the output as a
+// template.HTML value.
+func execTemplate(t *template.Template, name string, data interface{}) (template.HTML, error) {
+	b := new(bytes.Buffer)
+	err := t.ExecuteTemplate(b, name, data)
+	if err != nil {
+		return "", err
+	}
+	return template.HTML(b.String()), nil
+}
+
+// Text represents an optionally preformatted paragraph.
+type Text struct {
+	Lines []string
+	Pre   bool
+}
+
+func (t Text) TemplateName() string { return "text" }
+
+// List represents a bulleted list.
+type List struct {
+	Bullet []string
+}
+
+func (l List) TemplateName() string { return "list" }
+
+// Lines is a helper for parsing line-based input.
+type Lines struct {
+	line int // 0 indexed, so has 1-indexed number of last line returned
+	text []string
+}
+
+func readLines(r io.Reader) (*Lines, error) {
+	var lines []string
+	s := bufio.NewScanner(r)
+	for s.Scan() {
+		lines = append(lines, s.Text())
+	}
+	if err := s.Err(); err != nil {
+		return nil, err
+	}
+	return &Lines{0, lines}, nil
+}
+
+func (l *Lines) next() (text string, ok bool) {
+	for {
+		current := l.line
+		l.line++
+		if current >= len(l.text) {
+			return "", false
+		}
+		text = l.text[current]
+		// Lines starting with # are comments.
+		if len(text) == 0 || text[0] != '#' {
+			ok = true
+			break
+		}
+	}
+	return
+}
+
+func (l *Lines) back() {
+	l.line--
+}
+
+func (l *Lines) nextNonEmpty() (text string, ok bool) {
+	for {
+		text, ok = l.next()
+		if !ok {
+			return
+		}
+		if len(text) > 0 {
+			break
+		}
+	}
+	return
+}
+
+// A Context specifies the supporting context for parsing a presentation.
+type Context struct {
+	// ReadFile reads the file named by filename and returns the contents.
+	ReadFile func(filename string) ([]byte, error)
+}
+
+// ParseMode represents flags for the Parse function.
+type ParseMode int
+
+const (
+	// If set, parse only the title and subtitle.
+	TitlesOnly ParseMode = 1
+)
+
+// Parse parses a document from r.
+func (ctx *Context) Parse(r io.Reader, name string, mode ParseMode) (*Doc, error) {
+	doc := new(Doc)
+	lines, err := readLines(r)
+	if err != nil {
+		return nil, err
+	}
+
+	for i := lines.line; i < len(lines.text); i++ {
+		if strings.HasPrefix(lines.text[i], "*") {
+			break
+		}
+
+		if isSpeakerNote(lines.text[i]) {
+			doc.TitleNotes = append(doc.TitleNotes, lines.text[i][2:])
+		}
+	}
+
+	err = parseHeader(doc, lines)
+	if err != nil {
+		return nil, err
+	}
+	if mode&TitlesOnly != 0 {
+		return doc, nil
+	}
+
+	// Authors
+	if doc.Authors, err = parseAuthors(lines); err != nil {
+		return nil, err
+	}
+	// Sections
+	if doc.Sections, err = parseSections(ctx, name, lines, []int{}); err != nil {
+		return nil, err
+	}
+	return doc, nil
+}
+
+// Parse parses a document from r. Parse reads assets used by the presentation
+// from the file system using ioutil.ReadFile.
+func Parse(r io.Reader, name string, mode ParseMode) (*Doc, error) {
+	ctx := Context{ReadFile: ioutil.ReadFile}
+	return ctx.Parse(r, name, mode)
+}
+
+// isHeading matches any section heading.
+var isHeading = regexp.MustCompile(`^\*+ `)
+
+// lesserHeading returns true if text is a heading of a lesser or equal level
+// than that denoted by prefix.
+func lesserHeading(text, prefix string) bool {
+	return isHeading.MatchString(text) && !strings.HasPrefix(text, prefix+"*")
+}
+
+// parseSections parses Sections from lines for the section level indicated by
+// number (a nil number indicates the top level).
+func parseSections(ctx *Context, name string, lines *Lines, number []int) ([]Section, error) {
+	var sections []Section
+	for i := 1; ; i++ {
+		// Next non-empty line is title.
+		text, ok := lines.nextNonEmpty()
+		for ok && text == "" {
+			text, ok = lines.next()
+		}
+		if !ok {
+			break
+		}
+		prefix := strings.Repeat("*", len(number)+1)
+		if !strings.HasPrefix(text, prefix+" ") {
+			lines.back()
+			break
+		}
+		section := Section{
+			Number: append(append([]int{}, number...), i),
+			Title:  text[len(prefix)+1:],
+		}
+		text, ok = lines.nextNonEmpty()
+		for ok && !lesserHeading(text, prefix) {
+			var e Elem
+			r, _ := utf8.DecodeRuneInString(text)
+			switch {
+			case unicode.IsSpace(r):
+				i := strings.IndexFunc(text, func(r rune) bool {
+					return !unicode.IsSpace(r)
+				})
+				if i < 0 {
+					break
+				}
+				indent := text[:i]
+				var s []string
+				for ok && (strings.HasPrefix(text, indent) || text == "") {
+					if text != "" {
+						text = text[i:]
+					}
+					s = append(s, text)
+					text, ok = lines.next()
+				}
+				lines.back()
+				pre := strings.Join(s, "\n")
+				pre = strings.Replace(pre, "\t", "    ", -1) // browsers treat tabs badly
+				pre = strings.TrimRightFunc(pre, unicode.IsSpace)
+				e = Text{Lines: []string{pre}, Pre: true}
+			case strings.HasPrefix(text, "- "):
+				var b []string
+				for ok && strings.HasPrefix(text, "- ") {
+					b = append(b, text[2:])
+					text, ok = lines.next()
+				}
+				lines.back()
+				e = List{Bullet: b}
+			case isSpeakerNote(text):
+				section.Notes = append(section.Notes, text[2:])
+			case strings.HasPrefix(text, prefix+"* "):
+				lines.back()
+				subsecs, err := parseSections(ctx, name, lines, section.Number)
+				if err != nil {
+					return nil, err
+				}
+				for _, ss := range subsecs {
+					section.Elem = append(section.Elem, ss)
+				}
+			case strings.HasPrefix(text, "."):
+				args := strings.Fields(text)
+				if args[0] == ".background" {
+					section.Classes = append(section.Classes, "background")
+					section.Styles = append(section.Styles, "background-image: url('"+args[1]+"')")
+					break
+				}
+				parser := parsers[args[0]]
+				if parser == nil {
+					return nil, fmt.Errorf("%s:%d: unknown command %q\n", name, lines.line, text)
+				}
+				t, err := parser(ctx, name, lines.line, text)
+				if err != nil {
+					return nil, err
+				}
+				e = t
+			default:
+				var l []string
+				for ok && strings.TrimSpace(text) != "" {
+					if text[0] == '.' { // Command breaks text block.
+						lines.back()
+						break
+					}
+					if strings.HasPrefix(text, `\.`) { // Backslash escapes initial period.
+						text = text[1:]
+					}
+					l = append(l, text)
+					text, ok = lines.next()
+				}
+				if len(l) > 0 {
+					e = Text{Lines: l}
+				}
+			}
+			if e != nil {
+				section.Elem = append(section.Elem, e)
+			}
+			text, ok = lines.nextNonEmpty()
+		}
+		if isHeading.MatchString(text) {
+			lines.back()
+		}
+		sections = append(sections, section)
+	}
+	return sections, nil
+}
+
+func parseHeader(doc *Doc, lines *Lines) error {
+	var ok bool
+	// First non-empty line starts header.
+	doc.Title, ok = lines.nextNonEmpty()
+	if !ok {
+		return errors.New("unexpected EOF; expected title")
+	}
+	for {
+		text, ok := lines.next()
+		if !ok {
+			return errors.New("unexpected EOF")
+		}
+		if text == "" {
+			break
+		}
+		if isSpeakerNote(text) {
+			continue
+		}
+		const tagPrefix = "Tags:"
+		if strings.HasPrefix(text, tagPrefix) {
+			tags := strings.Split(text[len(tagPrefix):], ",")
+			for i := range tags {
+				tags[i] = strings.TrimSpace(tags[i])
+			}
+			doc.Tags = append(doc.Tags, tags...)
+		} else if t, ok := parseTime(text); ok {
+			doc.Time = t
+		} else if doc.Subtitle == "" {
+			doc.Subtitle = text
+		} else {
+			return fmt.Errorf("unexpected header line: %q", text)
+		}
+	}
+	return nil
+}
+
+func parseAuthors(lines *Lines) (authors []Author, err error) {
+	// This grammar demarcates authors with blanks.
+
+	// Skip blank lines.
+	if _, ok := lines.nextNonEmpty(); !ok {
+		return nil, errors.New("unexpected EOF")
+	}
+	lines.back()
+
+	var a *Author
+	for {
+		text, ok := lines.next()
+		if !ok {
+			return nil, errors.New("unexpected EOF")
+		}
+
+		// If we find a section heading, we're done.
+		if strings.HasPrefix(text, "* ") {
+			lines.back()
+			break
+		}
+
+		if isSpeakerNote(text) {
+			continue
+		}
+
+		// If we encounter a blank we're done with this author.
+		if a != nil && len(text) == 0 {
+			authors = append(authors, *a)
+			a = nil
+			continue
+		}
+		if a == nil {
+			a = new(Author)
+		}
+
+		// Parse the line. Those that
+		// - begin with @ are twitter names,
+		// - contain slashes are links, or
+		// - contain an @ symbol are an email address.
+		// The rest is just text.
+		var el Elem
+		switch {
+		case strings.HasPrefix(text, "@"):
+			el = parseURL("http://twitter.com/" + text[1:])
+		case strings.Contains(text, ":"):
+			el = parseURL(text)
+		case strings.Contains(text, "@"):
+			el = parseURL("mailto:" + text)
+		}
+		if l, ok := el.(Link); ok {
+			l.Label = text
+			el = l
+		}
+		if el == nil {
+			el = Text{Lines: []string{text}}
+		}
+		a.Elem = append(a.Elem, el)
+	}
+	if a != nil {
+		authors = append(authors, *a)
+	}
+	return authors, nil
+}
+
+func parseURL(text string) Elem {
+	u, err := url.Parse(text)
+	if err != nil {
+		log.Printf("Parse(%q): %v", text, err)
+		return nil
+	}
+	return Link{URL: u}
+}
+
+func parseTime(text string) (t time.Time, ok bool) {
+	t, err := time.Parse("15:04 2 Jan 2006", text)
+	if err == nil {
+		return t, true
+	}
+	t, err = time.Parse("2 Jan 2006", text)
+	if err == nil {
+		// at 11am UTC it is the same date everywhere
+		t = t.Add(time.Hour * 11)
+		return t, true
+	}
+	return time.Time{}, false
+}
+
+func isSpeakerNote(s string) bool {
+	return strings.HasPrefix(s, ": ")
+}
diff --git a/vendor/golang.org/x/tools/present/style.go b/vendor/golang.org/x/tools/present/style.go
new file mode 100644
index 0000000..e2c228e
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/style.go
@@ -0,0 +1,167 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import (
+	"bytes"
+	"html"
+	"html/template"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+)
+
+/*
+	Fonts are demarcated by an initial and final char bracketing a
+	space-delimited word, plus possibly some terminal punctuation.
+	The chars are
+		_ for italic
+		* for bold
+		` (back quote) for fixed width.
+	Inner appearances of the char become spaces. For instance,
+		_this_is_italic_!
+	becomes
+		this is italic!
+*/
+
+func init() {
+	funcs["style"] = Style
+}
+
+// Style returns s with HTML entities escaped and font indicators turned into
+// HTML font tags.
+func Style(s string) template.HTML {
+	return template.HTML(font(html.EscapeString(s)))
+}
+
+// font returns s with font indicators turned into HTML font tags.
+func font(s string) string {
+	if !strings.ContainsAny(s, "[`_*") {
+		return s
+	}
+	words := split(s)
+	var b bytes.Buffer
+Word:
+	for w, word := range words {
+		if len(word) < 2 {
+			continue Word
+		}
+		if link, _ := parseInlineLink(word); link != "" {
+			words[w] = link
+			continue Word
+		}
+		const marker = "_*`"
+		// Initial punctuation is OK but must be peeled off.
+		first := strings.IndexAny(word, marker)
+		if first == -1 {
+			continue Word
+		}
+		// Opening marker must be at the beginning of the token or else preceded by punctuation.
+		if first != 0 {
+			r, _ := utf8.DecodeLastRuneInString(word[:first])
+			if !unicode.IsPunct(r) {
+				continue Word
+			}
+		}
+		open, word := word[:first], word[first:]
+		char := word[0] // ASCII is OK.
+		close := ""
+		switch char {
+		default:
+			continue Word
+		case '_':
+			open += ""
+			close = ""
+		case '*':
+			open += ""
+			close = ""
+		case '`':
+			open += ""
+			close = ""
+		}
+		// Closing marker must be at the end of the token or else followed by punctuation.
+		last := strings.LastIndex(word, word[:1])
+		if last == 0 {
+			continue Word
+		}
+		if last+1 != len(word) {
+			r, _ := utf8.DecodeRuneInString(word[last+1:])
+			if !unicode.IsPunct(r) {
+				continue Word
+			}
+		}
+		head, tail := word[:last+1], word[last+1:]
+		b.Reset()
+		b.WriteString(open)
+		var wid int
+		for i := 1; i < len(head)-1; i += wid {
+			var r rune
+			r, wid = utf8.DecodeRuneInString(head[i:])
+			if r != rune(char) {
+				// Ordinary character.
+				b.WriteRune(r)
+				continue
+			}
+			if head[i+1] != char {
+				// Inner char becomes space.
+				b.WriteRune(' ')
+				continue
+			}
+			// Doubled char becomes real char.
+			// Not worth worrying about "_x__".
+			b.WriteByte(char)
+			wid++ // Consumed two chars, both ASCII.
+		}
+		b.WriteString(close) // Write closing tag.
+		b.WriteString(tail)  // Restore trailing punctuation.
+		words[w] = b.String()
+	}
+	return strings.Join(words, "")
+}
+
+// split is like strings.Fields but also returns the runs of spaces
+// and treats inline links as distinct words.
+func split(s string) []string {
+	var (
+		words = make([]string, 0, 10)
+		start = 0
+	)
+
+	// appendWord appends the string s[start:end] to the words slice.
+	// If the word contains the beginning of a link, the non-link portion
+	// of the word and the entire link are appended as separate words,
+	// and the start index is advanced to the end of the link.
+	appendWord := func(end int) {
+		if j := strings.Index(s[start:end], "[["); j > -1 {
+			if _, l := parseInlineLink(s[start+j:]); l > 0 {
+				// Append portion before link, if any.
+				if j > 0 {
+					words = append(words, s[start:start+j])
+				}
+				// Append link itself.
+				words = append(words, s[start+j:start+j+l])
+				// Advance start index to end of link.
+				start = start + j + l
+				return
+			}
+		}
+		// No link; just add the word.
+		words = append(words, s[start:end])
+		start = end
+	}
+
+	wasSpace := false
+	for i, r := range s {
+		isSpace := unicode.IsSpace(r)
+		if i > start && isSpace != wasSpace {
+			appendWord(i)
+		}
+		wasSpace = isSpace
+	}
+	for start < len(s) {
+		appendWord(len(s))
+	}
+	return words
+}
diff --git a/vendor/golang.org/x/tools/present/style_test.go b/vendor/golang.org/x/tools/present/style_test.go
new file mode 100644
index 0000000..cef5a62
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/style_test.go
@@ -0,0 +1,124 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func TestSplit(t *testing.T) {
+	var tests = []struct {
+		in  string
+		out []string
+	}{
+		{"", []string{}},
+		{" ", []string{" "}},
+		{"abc", []string{"abc"}},
+		{"abc def", []string{"abc", " ", "def"}},
+		{"abc def ", []string{"abc", " ", "def", " "}},
+		{"hey [[http://golang.org][Gophers]] around",
+			[]string{"hey", " ", "[[http://golang.org][Gophers]]", " ", "around"}},
+		{"A [[http://golang.org/doc][two words]] link",
+			[]string{"A", " ", "[[http://golang.org/doc][two words]]", " ", "link"}},
+		{"Visit [[http://golang.org/doc]] now",
+			[]string{"Visit", " ", "[[http://golang.org/doc]]", " ", "now"}},
+		{"not [[http://golang.org/doc][a [[link]] ]] around",
+			[]string{"not", " ", "[[http://golang.org/doc][a [[link]]", " ", "]]", " ", "around"}},
+		{"[[http://golang.org][foo bar]]",
+			[]string{"[[http://golang.org][foo bar]]"}},
+		{"ends with [[http://golang.org][link]]",
+			[]string{"ends", " ", "with", " ", "[[http://golang.org][link]]"}},
+		{"my talk ([[http://talks.golang.org/][slides here]])",
+			[]string{"my", " ", "talk", " ", "(", "[[http://talks.golang.org/][slides here]]", ")"}},
+	}
+	for _, test := range tests {
+		out := split(test.in)
+		if !reflect.DeepEqual(out, test.out) {
+			t.Errorf("split(%q):\ngot\t%q\nwant\t%q", test.in, out, test.out)
+		}
+	}
+}
+
+func TestFont(t *testing.T) {
+	var tests = []struct {
+		in  string
+		out string
+	}{
+		{"", ""},
+		{" ", " "},
+		{"\tx", "\tx"},
+		{"_a_", "a"},
+		{"*a*", "a"},
+		{"`a`", "a"},
+		{"_a_b_", "a b"},
+		{"_a__b_", "a_b"},
+		{"_a___b_", "a_ b"},
+		{"*a**b*?", "a*b?"},
+		{"_a_<>_b_.", "a <> b."},
+		{"(_a_)", "(a)"},
+		{"((_a_), _b_, _c_).", "((a), b, c)."},
+		{"(_a)", "(_a)"},
+		{"(_a)", "(_a)"},
+		{"_Why_use_scoped__ptr_? Use plain ***ptr* instead.", "Why use scoped_ptr? Use plain *ptr instead."},
+		{"_hey_ [[http://golang.org][*Gophers*]] *around*",
+			`hey Gophers around`},
+		{"_hey_ [[http://golang.org][so _many_ *Gophers*]] *around*",
+			`hey so many Gophers around`},
+		{"Visit [[http://golang.org]] now",
+			`Visit golang.org now`},
+		{"my talk ([[http://talks.golang.org/][slides here]])",
+			`my talk (slides here)`},
+		{"Markup—_especially_italic_text_—can easily be overused.",
+			`Markup—especially italic text—can easily be overused.`},
+		{"`go`get`'s codebase", // ascii U+0027 ' before s
+			`go get's codebase`},
+		{"`go`get`’s codebase", // unicode right single quote U+2019 ’ before s
+			`go get’s codebase`},
+		{"a_variable_name",
+			`a_variable_name`},
+	}
+	for _, test := range tests {
+		out := font(test.in)
+		if out != test.out {
+			t.Errorf("font(%q):\ngot\t%q\nwant\t%q", test.in, out, test.out)
+		}
+	}
+}
+
+func TestStyle(t *testing.T) {
+	var tests = []struct {
+		in  string
+		out string
+	}{
+		{"", ""},
+		{" ", " "},
+		{"\tx", "\tx"},
+		{"_a_", "a"},
+		{"*a*", "a"},
+		{"`a`", "a"},
+		{"_a_b_", "a b"},
+		{"_a__b_", "a_b"},
+		{"_a___b_", "a_ b"},
+		{"*a**b*?", "a*b?"},
+		{"_a_<>_b_.", "a <> b."},
+		{"(_a_<>_b_)", "(a <> b)"},
+		{"((_a_), _b_, _c_).", "((a), b, c)."},
+		{"(_a)", "(_a)"},
+	}
+	for _, test := range tests {
+		out := string(Style(test.in))
+		if out != test.out {
+			t.Errorf("style(%q):\ngot\t%q\nwant\t%q", test.in, out, test.out)
+		}
+	}
+}
+
+func ExampleStyle() {
+	const s = "*Gophers* are _clearly_ > *cats*!"
+	fmt.Println(Style(s))
+	// Output: Gophers are clearly > cats!
+}
diff --git a/vendor/golang.org/x/tools/present/video.go b/vendor/golang.org/x/tools/present/video.go
new file mode 100644
index 0000000..913822e
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/video.go
@@ -0,0 +1,51 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import (
+	"fmt"
+	"strings"
+)
+
+func init() {
+	Register("video", parseVideo)
+}
+
+type Video struct {
+	URL        string
+	SourceType string
+	Width      int
+	Height     int
+}
+
+func (v Video) TemplateName() string { return "video" }
+
+func parseVideo(ctx *Context, fileName string, lineno int, text string) (Elem, error) {
+	args := strings.Fields(text)
+	vid := Video{URL: args[1], SourceType: args[2]}
+	a, err := parseArgs(fileName, lineno, args[3:])
+	if err != nil {
+		return nil, err
+	}
+	switch len(a) {
+	case 0:
+		// no size parameters
+	case 2:
+		// If a parameter is empty (underscore) or invalid
+		// leave the field set to zero. The "video" action
+		// template will then omit that vid tag attribute and
+		// the browser will calculate the value to preserve
+		// the aspect ratio.
+		if v, ok := a[0].(int); ok {
+			vid.Height = v
+		}
+		if v, ok := a[1].(int); ok {
+			vid.Width = v
+		}
+	default:
+		return nil, fmt.Errorf("incorrect video invocation: %q", text)
+	}
+	return vid, nil
+}
-- 
cgit v1.2.3