diff --git a/Gopkg.lock b/Gopkg.lock index 598347b..7819602 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -55,12 +55,24 @@ packages = ["."] revision = "be5337e7b39e64e5f91445ce7e721888dbab7387" +[[projects]] + branch = "master" + name = "github.com/pkg/browser" + packages = ["."] + revision = "c90ca0c84f15f81c982e32665bffd8d7aac8f097" + [[projects]] name = "github.com/pkg/errors" packages = ["."] revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" +[[projects]] + name = "github.com/rakyll/statik" + packages = ["fs"] + revision = "fd36b3595eb2ec8da4b8153b107f7ea08504899d" + version = "v0.1.1" + [[projects]] branch = "master" name = "github.com/spf13/cobra" @@ -82,6 +94,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "0ddb4a7c5d25d545c99a842937dc69633db1fe70a17e56f25ddf7105166ceedf" + inputs-digest = "3e2c6e437a2a7fc43e67d6734a90b7c916987de190ed20d4ae74d3e8be4c6910" solver-name = "gps-cdcl" solver-version = 1 diff --git a/README.md b/README.md index d7fc789..4378dbf 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ go get gnorm.org/gnorm ``` However, using go get to install will mean that `gnorm version` doesn't report -the correct data. +the correct data, and `gnorm docs` won't show you the docs in your browser. For best results, use the go "makefile" in the root of the repo, which will do all the build-time magic: diff --git a/cli/commands.go b/cli/commands.go index 5e8c9f2..43613fd 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "gnorm.org/gnorm/environ" "gnorm.org/gnorm/run" ) @@ -111,6 +112,19 @@ Creates a default gnorm.toml and the various template files needed to run GNORM. } } +func docCmd(env environ.Values) *cobra.Command { + return &cobra.Command{ + Use: "docs", + Short: "Runs a local webserver serving gnorm documentation.", + Long: ` +Starts a web server running at localhost:8080 that serves docs for this version +of Gnorm.`[1:], + RunE: func(cmd *cobra.Command, args []string) error { + return showDocs(env, cmd, args) + }, + } +} + func createFile(name, contents string) error { f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) if err != nil { diff --git a/cli/docs_make.go b/cli/docs_make.go new file mode 100644 index 0000000..298bf3d --- /dev/null +++ b/cli/docs_make.go @@ -0,0 +1,40 @@ +//+build make + +package cli + +import ( + "fmt" + "log" + "net/http" + + "github.com/pkg/browser" + "github.com/pkg/errors" + "github.com/rakyll/statik/fs" + "github.com/spf13/cobra" + + _ "gnorm.org/gnorm/cli/statik" + "gnorm.org/gnorm/environ" +) + +func showDocs(env environ.Values, cmd *cobra.Command, args []string) error { + // this folder gets briefly copied here during go run make.go + statikFS, err := fs.New() + if err != nil { + log.Fatal(err) + } + + http.Handle("/", http.FileServer(statikFS)) + fmt.Fprintln(env.Stdout, "serving docs at http://localhost:8080") + fmt.Fprintln(env.Stdout, "hit ctrl-C to cancel") + go func() { + if err := browser.OpenURL("http://localhost:8080"); err != nil { + fmt.Println("failed to open browser") + } + }() + err = http.ListenAndServe(":8080", nil) + if err != nil { + return codeErr{errors.WithMessage(err, "can't serve docs"), 1} + } + + return nil +} diff --git a/cli/docs_nomake.go b/cli/docs_nomake.go new file mode 100644 index 0000000..0aec9c3 --- /dev/null +++ b/cli/docs_nomake.go @@ -0,0 +1,17 @@ +//+build !make + +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "gnorm.org/gnorm/environ" +) + +func showDocs(env environ.Values, cmd *cobra.Command, args []string) error { + fmt.Fprintln(env.Stdout, "docs not available, you need to build with go run make.go") + + return nil +} diff --git a/cli/run.go b/cli/run.go index 18d2573..f489b80 100644 --- a/cli/run.go +++ b/cli/run.go @@ -52,6 +52,7 @@ runnable code. See full docs at https://gnorm.org`[1:], rootCmd.AddCommand(genCmd(env)) rootCmd.AddCommand(versionCmd(env)) rootCmd.AddCommand(initCmd(env)) + rootCmd.AddCommand(docCmd(env)) return code(rootCmd.Execute()) } diff --git a/main.go b/main.go index 53b1be3..36db3cf 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( //go:generate gocog ./site/content/cli/commands/version.md --startmark={{{ --endmark=}}} //go:generate gocog ./site/content/cli/commands/init.md --startmark={{{ --endmark=}}} //go:generate gocog ./site/content/cli/commands/gen.md --startmark={{{ --endmark=}}} +//go:generate gocog ./site/content/cli/commands/docs.md --startmark={{{ --endmark=}}} //go:generate gocog ./site/content/templates/functions.md --startmark={{{ --endmark=}}} //go:generate gocog ./site/content/cli/configuration.md --startmark={{{ --endmark=}}} diff --git a/make.go b/make.go index 52a0adb..b3bfb06 100644 --- a/make.go +++ b/make.go @@ -5,75 +5,127 @@ package main import ( + "flag" "fmt" + "io/ioutil" + "log" "os" "os/exec" "strings" "time" ) -const usage = `usage: go run make.go [command] +const usage = ` +usage: go run make.go [flags] [command] make.go is the build script for gnorm. +flags: + + -v show verbose output + commands: install compile with go install [default] all build for all supported platforms help display this help ` +var verbose = false + func main() { - switch len(os.Args) { + log.SetFlags(0) + flag.BoolVar(&verbose, "v", false, "") + flag.Parse() + if !verbose { + log.SetOutput(ioutil.Discard) + } + if len(flag.Args()) > 1 { + Fatal("invalid args" + usage) + } + + genSite() + log.Print("downloading statik") + run("go", "get", "github.com/rakyll/statik") + log.Print("generating statik embedded files") + run("statik", "-f", "-src", "./cli/public", "-dest", "./cli") + defer func() { + log.Print("removing generated hugo site") + mustRemove("./cli/public") + log.Print("removing generated statik package") + mustRemove("./cli/statik") + }() + switch len(flag.Args()) { + case 0: + // (default) + log.Print("running go install") + run("go", "install", "-tags", "make", "--ldflags="+flags(), "gnorm.org/gnorm") case 1: - fmt.Print(run("go", "install", "--ldflags="+flags(), "gnorm.org/gnorm")) - case 2: - switch os.Args[1] { + switch flag.Args()[1] { case "install": - fmt.Print(run("go", "install", "--ldflags="+flags(), "gnorm.org/gnorm")) + log.Print("running go install") + run("go", "install", "-tags", "make", "--ldflags="+flags(), "gnorm.org/gnorm") case "all": - ldf := flags() - for _, OS := range []string{"windows", "darwin", "linux"} { - if err := os.Setenv("GOOS", OS); err != nil { - fmt.Println(err) - os.Exit(1) - } - for _, ARCH := range []string{"amd64", "386"} { - if err := os.Setenv("GOOS", OS); err != nil { - fmt.Println(err) - os.Exit(1) - } - fmt.Print(run("go", "build", "-o", "gnorm_"+OS+"_"+ARCH, "--ldflags="+ldf, "gnorm.org/gnorm")) - } - } + makeAll() case "help": - fmt.Println(usage) + log.Print(usage) default: - fmt.Println(usage) - os.Exit(1) + Fatal(usage) + } + default: + // we already checked for this, but just in case + Fatal("invalid args" + usage) + } +} + +func makeAll() { + ldf := flags() + for _, OS := range []string{"windows", "darwin", "linux"} { + if err := os.Setenv("GOOS", OS); err != nil { + Fatal(err) + } + for _, ARCH := range []string{"amd64", "386"} { + if err := os.Setenv("GOOS", OS); err != nil { + Fatal(err) + } + log.Printf("running go build for GOOS=%s GOARCH=%s", OS, ARCH) + run("go", "build", "-tags", "make", "-o", "gnorm_"+OS+"_"+ARCH, "--ldflags="+ldf) } } } func flags() string { timestamp := time.Now().Format(time.RFC3339) - hash := run("git", "rev-parse", "HEAD") + hash := output("git", "rev-parse", "HEAD") version := gitTag() return fmt.Sprintf(`-X "gnorm.org/gnorm/cli.timestamp=%s" -X "gnorm.org/gnorm/cli.commitHash=%s" -X "gnorm.org/gnorm/cli.version=%s"`, timestamp, hash, version) } -func run(cmd string, args ...string) string { +func run(cmd string, args ...string) { + c := exec.Command(cmd, args...) + c.Stderr = os.Stderr + if verbose { + c.Stdout = os.Stdout + } + err := c.Run() + if err != nil { + Fatal(err) + } +} + +func output(cmd string, args ...string) string { c := exec.Command(cmd, args...) c.Stderr = os.Stderr b, err := c.Output() if err != nil { - fmt.Print(string(b)) - os.Exit(1) + log.Print(string(b)) + Fatal(err) } return string(b) } func gitTag() string { c := exec.Command("git", "describe", "--tags") + c.Stderr = os.Stderr b, err := c.Output() if err != nil { exit, ok := err.(*exec.ExitError) @@ -81,9 +133,33 @@ func gitTag() string { // probably no git tag return "dev" } - fmt.Print(string(b)) - os.Exit(1) + Fatal(string(b)) } return strings.TrimSuffix(string(b), "\n") } + +func genSite() { + log.Print("cleaning up any existing hugo generated files") + mustRemove("./cli/public") + log.Print("downloading hugo") + run("go", "get", "github.com/gohugoio/hugo") + log.Print("generating docs site") + run("hugo", "-s", "./site", "-d", "../cli/public") + log.Print("removing fonts from generated site") + // fonts are BIG + mustRemove("./cli/public/fonts") + mustRemove("./cli/public/revealjs/lib/font") +} + +func mustRemove(s string) { + err := os.RemoveAll(s) + if !os.IsNotExist(err) && err != nil { + log.Fatal(err) + } +} + +func Fatal(args ...interface{}) { + log.SetOutput(os.Stdout) + log.Fatal(args...) +} diff --git a/site/content/cli/_index.md b/site/content/cli/_index.md index 0a87f45..a7076ca 100644 --- a/site/content/cli/_index.md +++ b/site/content/cli/_index.md @@ -29,6 +29,7 @@ Usage: gnorm [command] Available Commands: + docs Runs a local webserver serving gnorm documentation. gen Generate code from DB schema help Help about any command init Generates the files needed to run GNORM. diff --git a/site/content/cli/commands/docs.md b/site/content/cli/commands/docs.md new file mode 100644 index 0000000..c027d4e --- /dev/null +++ b/site/content/cli/commands/docs.md @@ -0,0 +1,39 @@ ++++ +title= "docs" +date= 2017-08-17T13:16:04-04:00 +description = "" ++++ + + +``` +gnorm docs + +Starts a web server running at localhost:8080 that serves docs for this version +of Gnorm. + +Usage: + gnorm docs [flags] + +Flags: + -h, --help help for docs +``` + \ No newline at end of file diff --git a/vendor/github.com/pkg/browser/LICENSE b/vendor/github.com/pkg/browser/LICENSE new file mode 100644 index 0000000..65f78fb --- /dev/null +++ b/vendor/github.com/pkg/browser/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2014, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/browser/README.md b/vendor/github.com/pkg/browser/README.md new file mode 100644 index 0000000..72b1976 --- /dev/null +++ b/vendor/github.com/pkg/browser/README.md @@ -0,0 +1,55 @@ + +# browser + import "github.com/pkg/browser" + +Package browser provides helpers to open files, readers, and urls in a browser window. + +The choice of which browser is started is entirely client dependant. + + + + + +## Variables +``` go +var Stderr io.Writer = os.Stderr +``` +Stderr is the io.Writer to which executed commands write standard error. + +``` go +var Stdout io.Writer = os.Stdout +``` +Stdout is the io.Writer to which executed commands write standard output. + + +## func OpenFile +``` go +func OpenFile(path string) error +``` +OpenFile opens new browser window for the file path. + + +## func OpenReader +``` go +func OpenReader(r io.Reader) error +``` +OpenReader consumes the contents of r and presents the +results in a new browser window. + + +## func OpenURL +``` go +func OpenURL(url string) error +``` +OpenURL opens a new browser window pointing to url. + + + + + + + + + +- - - +Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) diff --git a/vendor/github.com/pkg/browser/browser.go b/vendor/github.com/pkg/browser/browser.go new file mode 100644 index 0000000..d92c4cd --- /dev/null +++ b/vendor/github.com/pkg/browser/browser.go @@ -0,0 +1,62 @@ +// Package browser provides helpers to open files, readers, and urls in a browser window. +// +// The choice of which browser is started is entirely client dependant. +package browser + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" +) + +// Stdout is the io.Writer to which executed commands write standard output. +var Stdout io.Writer = os.Stdout + +// Stderr is the io.Writer to which executed commands write standard error. +var Stderr io.Writer = os.Stderr + +// OpenFile opens new browser window for the file path. +func OpenFile(path string) error { + path, err := filepath.Abs(path) + if err != nil { + return err + } + return OpenURL("file://" + path) +} + +// OpenReader consumes the contents of r and presents the +// results in a new browser window. +func OpenReader(r io.Reader) error { + f, err := ioutil.TempFile("", "browser") + if err != nil { + return fmt.Errorf("browser: could not create temporary file: %v", err) + } + if _, err := io.Copy(f, r); err != nil { + f.Close() + return fmt.Errorf("browser: caching temporary file failed: %v", err) + } + if err := f.Close(); err != nil { + return fmt.Errorf("browser: caching temporary file failed: %v", err) + } + oldname := f.Name() + newname := oldname + ".html" + if err := os.Rename(oldname, newname); err != nil { + return fmt.Errorf("browser: renaming temporary file failed: %v", err) + } + return OpenFile(newname) +} + +// OpenURL opens a new browser window pointing to url. +func OpenURL(url string) error { + return openBrowser(url) +} + +func runCmd(prog string, args ...string) error { + cmd := exec.Command(prog, args...) + cmd.Stdout = Stdout + cmd.Stderr = Stderr + return cmd.Run() +} diff --git a/vendor/github.com/pkg/browser/browser_darwin.go b/vendor/github.com/pkg/browser/browser_darwin.go new file mode 100644 index 0000000..8507cf7 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_darwin.go @@ -0,0 +1,5 @@ +package browser + +func openBrowser(url string) error { + return runCmd("open", url) +} diff --git a/vendor/github.com/pkg/browser/browser_linux.go b/vendor/github.com/pkg/browser/browser_linux.go new file mode 100644 index 0000000..bed47dd --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_linux.go @@ -0,0 +1,5 @@ +package browser + +func openBrowser(url string) error { + return runCmd("xdg-open", url) +} diff --git a/vendor/github.com/pkg/browser/browser_openbsd.go b/vendor/github.com/pkg/browser/browser_openbsd.go new file mode 100644 index 0000000..4fc7ff0 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_openbsd.go @@ -0,0 +1,14 @@ +package browser + +import ( + "errors" + "os/exec" +) + +func openBrowser(url string) error { + err := runCmd("xdg-open", url) + if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { + return errors.New("xdg-open: command not found - install xdg-utils from ports(8)") + } + return err +} diff --git a/vendor/github.com/pkg/browser/browser_unsupported.go b/vendor/github.com/pkg/browser/browser_unsupported.go new file mode 100644 index 0000000..e29d220 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_unsupported.go @@ -0,0 +1,12 @@ +// +build !linux,!windows,!darwin,!openbsd + +package browser + +import ( + "fmt" + "runtime" +) + +func openBrowser(url string) error { + return fmt.Errorf("openBrowser: unsupported operating system: %v", runtime.GOOS) +} diff --git a/vendor/github.com/pkg/browser/browser_windows.go b/vendor/github.com/pkg/browser/browser_windows.go new file mode 100644 index 0000000..f65e0ee --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_windows.go @@ -0,0 +1,10 @@ +package browser + +import ( + "strings" +) + +func openBrowser(url string) error { + r := strings.NewReplacer("&", "^&") + return runCmd("cmd", "/c", "start", r.Replace(url)) +} diff --git a/vendor/github.com/pkg/browser/example_test.go b/vendor/github.com/pkg/browser/example_test.go new file mode 100644 index 0000000..a6fbe16 --- /dev/null +++ b/vendor/github.com/pkg/browser/example_test.go @@ -0,0 +1,23 @@ +package browser + +import "strings" + +func ExampleOpenFile() { + OpenFile("index.html") +} + +func ExampleOpenReader() { + // https://github.com/rust-lang/rust/issues/13871 + const quote = `There was a night when winds from unknown spaces +whirled us irresistibly into limitless vacum beyond all thought and entity. +Perceptions of the most maddeningly untransmissible sort thronged upon us; +perceptions of infinity which at the time convulsed us with joy, yet which +are now partly lost to my memory and partly incapable of presentation to others.` + r := strings.NewReader(quote) + OpenReader(r) +} + +func ExampleOpenURL() { + const url = "http://golang.org/" + OpenURL(url) +} diff --git a/vendor/github.com/pkg/browser/examples/Open/main.go b/vendor/github.com/pkg/browser/examples/Open/main.go new file mode 100644 index 0000000..fe73fe0 --- /dev/null +++ b/vendor/github.com/pkg/browser/examples/Open/main.go @@ -0,0 +1,50 @@ +// Open is a simple example of the github.com/pkg/browser package. +// +// Usage: +// +// # Open a file in a browser window +// Open $FILE +// +// # Open a URL in a browser window +// Open $URL +// +// # Open the contents of stdin in a browser window +// cat $SOMEFILE | Open +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/pkg/browser" +) + +func usage() { + fmt.Fprintf(os.Stderr, "Usage:\n %s [file]\n", os.Args[0]) + flag.PrintDefaults() +} + +func init() { + flag.Usage = usage + flag.Parse() +} + +func check(err error) { + if err != nil { + log.Fatal(err) + } +} + +func main() { + args := flag.Args() + switch len(args) { + case 0: + check(browser.OpenReader(os.Stdin)) + case 1: + check(browser.OpenFile(args[0])) + default: + usage() + } +} diff --git a/vendor/github.com/rakyll/statik/.travis.yml b/vendor/github.com/rakyll/statik/.travis.yml new file mode 100644 index 0000000..32a336a --- /dev/null +++ b/vendor/github.com/rakyll/statik/.travis.yml @@ -0,0 +1,10 @@ +language: go + +go: + - 1.7 + - 1.8 + - tip + +install: + - go build -v + - ./statik -src=./example/public -dest=./example/ diff --git a/vendor/github.com/rakyll/statik/LICENSE b/vendor/github.com/rakyll/statik/LICENSE new file mode 100644 index 0000000..a4c5efd --- /dev/null +++ b/vendor/github.com/rakyll/statik/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor 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, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/rakyll/statik/README.md b/vendor/github.com/rakyll/statik/README.md new file mode 100644 index 0000000..37f7c7e --- /dev/null +++ b/vendor/github.com/rakyll/statik/README.md @@ -0,0 +1,40 @@ +# statik + +[![Build Status](https://travis-ci.org/rakyll/statik.svg?branch=master)](https://travis-ci.org/rakyll/statik) + +statik allows you to embed a directory of static files into your Go binary to be later served from an http.FileSystem. + +Is this a crazy idea? No, not necessarily. If you're building a tool that has a Web component, you typically want to serve some images, CSS and JavaScript. You like the comfort of distributing a single binary, so you don't want to mess with deploying them elsewhere. If your static files are not large in size and will be browsed by a few people, statik is a solution you are looking for. + +## Usage + +Install the command line tool first. + + go get github.com/rakyll/statik + +statik is a tiny program that reads a directory and generates a source file contains its contents. The generated source file registers the directory contents to be used by statik file system. + +The command below will walk on the public path and generate a package called `statik` under the current working directory. + + $ statik -src=/path/to/your/project/public + +In your program, all your need to do is to import the generated package, initialize a new statik file system and serve. + +~~~ go +import ( + "github.com/rakyll/statik/fs" + + _ "./statik" // TODO: Replace with the absolute import path +) + +// ... + +statikFS, _ := fs.New() +http.ListenAndServe(":8080", http.FileServer(statikFS)) +~~~ + +Visit http://localhost:8080/path/to/file to see your file. + +There is also a working example under [example](https://github.com/rakyll/statik/tree/master/example) directory, follow the instructions to build and run it. + +Note: The idea and the implementation are hijacked from [camlistore](http://camlistore.org/). I decided to decouple it from its codebase due to the fact I'm actively in need of a similar solution for many of my projects. ![Analytics](https://ga-beacon.appspot.com/UA-46881978-1/statik?pixel) diff --git a/vendor/github.com/rakyll/statik/example/README.md b/vendor/github.com/rakyll/statik/example/README.md new file mode 100644 index 0000000..0945740 --- /dev/null +++ b/vendor/github.com/rakyll/statik/example/README.md @@ -0,0 +1,11 @@ +# How to run? + +Run `go generate` to create a statik package that embeds the binary data underneath the public directory. + + $ go generate + +Once the statik package is generated, run the web server: + + $ go run main.go + +Visit [http://localhost:8080/public/hello.txt](http://localhost:8080/public/hello.txt) to see the file. diff --git a/vendor/github.com/rakyll/statik/example/main.go b/vendor/github.com/rakyll/statik/example/main.go new file mode 100644 index 0000000..84f5cb1 --- /dev/null +++ b/vendor/github.com/rakyll/statik/example/main.go @@ -0,0 +1,23 @@ +//go:generate statik -src=./public + +package main + +import ( + "log" + "net/http" + + _ "github.com/rakyll/statik/example/statik" + "github.com/rakyll/statik/fs" +) + +// Before buildling, run go generate. +// Then, run the main program and visit http://localhost:8080/public/hello.txt +func main() { + statikFS, err := fs.New() + if err != nil { + log.Fatalf(err.Error()) + } + + http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(statikFS))) + http.ListenAndServe(":8080", nil) +} diff --git a/vendor/github.com/rakyll/statik/example/public/hello.txt b/vendor/github.com/rakyll/statik/example/public/hello.txt new file mode 100644 index 0000000..9801343 --- /dev/null +++ b/vendor/github.com/rakyll/statik/example/public/hello.txt @@ -0,0 +1,2 @@ +Hello World + diff --git a/vendor/github.com/rakyll/statik/example/public/img/friends.jpg b/vendor/github.com/rakyll/statik/example/public/img/friends.jpg new file mode 100644 index 0000000..886ba39 Binary files /dev/null and b/vendor/github.com/rakyll/statik/example/public/img/friends.jpg differ diff --git a/vendor/github.com/rakyll/statik/fs/fs.go b/vendor/github.com/rakyll/statik/fs/fs.go new file mode 100644 index 0000000..82cda3a --- /dev/null +++ b/vendor/github.com/rakyll/statik/fs/fs.go @@ -0,0 +1,146 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package fs contains an HTTP file system that works with zip contents. +package fs + +import ( + "archive/zip" + "bytes" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" +) + +var zipData string + +// file holds unzipped read-only file contents and file metadata. +type file struct { + os.FileInfo + data []byte +} + +type statikFS struct { + files map[string]file +} + +// Register registers zip contents data, later used to initialize +// the statik file system. +func Register(data string) { + zipData = data +} + +// New creates a new file system with the registered zip contents data. +// It unzips all files and stores them in an in-memory map. +func New() (http.FileSystem, error) { + if zipData == "" { + return nil, errors.New("statik/fs: no zip data registered") + } + zipReader, err := zip.NewReader(strings.NewReader(zipData), int64(len(zipData))) + if err != nil { + return nil, err + } + files := make(map[string]file) + for _, zipFile := range zipReader.File { + unzipped, err := unzip(zipFile) + if err != nil { + return nil, fmt.Errorf("statik/fs: error unzipping file %q: %s", zipFile.Name, err) + } + files["/"+zipFile.Name] = file{ + FileInfo: zipFile.FileInfo(), + data: unzipped, + } + } + return &statikFS{files: files}, nil +} + +func unzip(zf *zip.File) ([]byte, error) { + rc, err := zf.Open() + if err != nil { + return nil, err + } + defer rc.Close() + return ioutil.ReadAll(rc) +} + +// Open returns a file matching the given file name, or os.ErrNotExists if +// no file matching the given file name is found in the archive. +// If a directory is requested, Open returns the file named "index.html" +// in the requested directory, if that file exists. +func (fs *statikFS) Open(name string) (http.File, error) { + name = strings.Replace(name, "//", "/", -1) + f, ok := fs.files[name] + if ok { + return newHTTPFile(f, false), nil + } + // The file doesn't match, but maybe it's a directory, + // thus we should look for index.html + indexName := strings.Replace(name+"/index.html", "//", "/", -1) + f, ok = fs.files[indexName] + if !ok { + return nil, os.ErrNotExist + } + return newHTTPFile(f, true), nil +} + +func newHTTPFile(file file, isDir bool) *httpFile { + return &httpFile{ + file: file, + reader: bytes.NewReader(file.data), + isDir: isDir, + } +} + +// httpFile represents an HTTP file and acts as a bridge +// between file and http.File. +type httpFile struct { + file + + reader *bytes.Reader + isDir bool +} + +// Read reads bytes into p, returns the number of read bytes. +func (f *httpFile) Read(p []byte) (n int, err error) { + return f.reader.Read(p) +} + +// Seek seeks to the offset. +func (f *httpFile) Seek(offset int64, whence int) (ret int64, err error) { + return f.reader.Seek(offset, whence) +} + +// Stat stats the file. +func (f *httpFile) Stat() (os.FileInfo, error) { + return f, nil +} + +// IsDir returns true if the file location represents a directory. +func (f *httpFile) IsDir() bool { + return f.isDir +} + +// Readdir returns an empty slice of files, directory +// listing is disabled. +func (f *httpFile) Readdir(count int) ([]os.FileInfo, error) { + // directory listing is disabled. + return make([]os.FileInfo, 0), nil +} + +func (f *httpFile) Close() error { + return nil +} diff --git a/vendor/github.com/rakyll/statik/statik.go b/vendor/github.com/rakyll/statik/statik.go new file mode 100644 index 0000000..1a0d2f9 --- /dev/null +++ b/vendor/github.com/rakyll/statik/statik.go @@ -0,0 +1,227 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package contains a program that generates code to register +// a directory and its contents as zip data for statik file system. +package main + +import ( + "archive/zip" + "bytes" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +const ( + namePackage = "statik" + nameSourceFile = "statik.go" +) + +var ( + flagSrc = flag.String("src", path.Join(".", "public"), "The path of the source directory.") + flagDest = flag.String("dest", ".", "The destination path of the generated package.") + flagNoMtime = flag.Bool("m", false, "Ignore modification times on files.") + flagNoCompress = flag.Bool("Z", false, "Do not use compression to shrink the files.") + flagForce = flag.Bool("f", false, "Overwrite destination file if it already exists.") +) + +// mtimeDate holds the arbitrary mtime that we assign to files when +// flagNoMtime is set. +var mtimeDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) + +func main() { + flag.Parse() + + file, err := generateSource(*flagSrc) + if err != nil { + exitWithError(err) + } + + destDir := path.Join(*flagDest, namePackage) + err = os.MkdirAll(destDir, 0755) + if err != nil { + exitWithError(err) + } + + err = rename(file.Name(), path.Join(destDir, nameSourceFile)) + if err != nil { + exitWithError(err) + } +} + +// rename tries to os.Rename, but fall backs to copying from src +// to dest and unlink the source if os.Rename fails. +func rename(src, dest string) error { + // Try to rename generated source. + if err := os.Rename(src, dest); err == nil { + return nil + } + // If the rename failed (might do so due to temporary file residing on a + // different device), try to copy byte by byte. + rc, err := os.Open(src) + if err != nil { + return err + } + defer func() { + rc.Close() + os.Remove(src) // ignore the error, source is in tmp. + }() + + if _, err = os.Stat(dest); !os.IsNotExist(err) { + if *flagForce { + if err = os.Remove(dest); err != nil { + return fmt.Errorf("file %q could not be deleted", dest) + } + } else { + return fmt.Errorf("file %q already exists; use -f to overwrite", dest) + } + } + + wc, err := os.Create(dest) + if err != nil { + return err + } + defer wc.Close() + + if _, err = io.Copy(wc, rc); err != nil { + // Delete remains of failed copy attempt. + os.Remove(dest) + } + return err +} + +// Walks on the source path and generates source code +// that contains source directory's contents as zip contents. +// Generates source registers generated zip contents data to +// be read by the statik/fs HTTP file system. +func generateSource(srcPath string) (file *os.File, err error) { + var ( + buffer bytes.Buffer + zipWriter io.Writer + ) + + zipWriter = &buffer + f, err := ioutil.TempFile("", namePackage) + if err != nil { + return + } + + zipWriter = io.MultiWriter(zipWriter, f) + defer f.Close() + + w := zip.NewWriter(zipWriter) + if err = filepath.Walk(srcPath, func(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + // Ignore directories and hidden files. + // No entry is needed for directories in a zip file. + // Each file is represented with a path, no directory + // entities are required to build the hierarchy. + if fi.IsDir() || strings.HasPrefix(fi.Name(), ".") { + return nil + } + relPath, err := filepath.Rel(srcPath, path) + if err != nil { + return err + } + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + fHeader, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + if *flagNoMtime { + // Always use the same modification time so that + // the output is deterministic with respect to the file contents. + fHeader.SetModTime(mtimeDate) + } + fHeader.Name = filepath.ToSlash(relPath) + if !*flagNoCompress { + fHeader.Method = zip.Deflate + } + f, err := w.CreateHeader(fHeader) + if err != nil { + return err + } + _, err = f.Write(b) + return err + }); err != nil { + return + } + if err = w.Close(); err != nil { + return + } + + // then embed it as a quoted string + var qb bytes.Buffer + fmt.Fprintf(&qb, `// Code generated by statik. DO NOT EDIT. + +package %s + +import ( + "github.com/rakyll/statik/fs" +) + +func init() { + data := "`, namePackage) + FprintZipData(&qb, buffer.Bytes()) + fmt.Fprint(&qb, `" + fs.Register(data) +} +`) + + if err = ioutil.WriteFile(f.Name(), qb.Bytes(), 0644); err != nil { + return + } + return f, nil +} + +// FprintZipData converts zip binary contents to a string literal. +func FprintZipData(dest *bytes.Buffer, zipData []byte) { + for _, b := range zipData { + if b == '\n' { + dest.WriteString(`\n`) + continue + } + if b == '\\' { + dest.WriteString(`\\`) + continue + } + if b == '"' { + dest.WriteString(`\"`) + continue + } + if (b >= 32 && b <= 126) || b == '\t' { + dest.WriteByte(b) + continue + } + fmt.Fprintf(dest, "\\x%02x", b) + } +} + +// Prints out the error message and exists with a non-success signal. +func exitWithError(err error) { + fmt.Println(err) + os.Exit(1) +}