Commits

Miki Tebeka committed c7339f1 Merge

nrsc as go executable

Comments (0)

Files changed (6)

+# `nrsc` - Resource Compiler for Go
+
+`nrsc`  a directory of resource into a Go source file so you can still
+deploy a single executable as a web server with all the CSS, image files, JS ...
+included.
+
+
+## Installing
+
+    go get bitbucket.org/tebeka/nrsc
+    go get bitbucket.org/tebeka/nrsc/nrsc
+
+(The 2'nd command will grab the nrsc executable)
+
+And you'll need `zip` somewhere in your path.
+
+## Invocation
+
+    go build
+    nrsc <executable> <resource dir> [zip options]
+
+
+## API
+The `nrsc` package has the following interface
+
+### nrsc.Handle(prefix string)
+
+This will register with the `net/http` module to handle all paths starting with prefix. 
+
+When a request is handled, `prefix` is stripped and then a resource is
+located and served.
+
+Resource that are not found will cause an HTTP 404 response.
+
+### nrsc.Get(path string) Resource
+
+Will return a resource interface (or `nil` if not found) (see resource interface below).
+
+This allows you more control on how to serve.
+
+
+### LoadTemplates(t *template.Template, filenames ...string) (*template.Template, error)
+
+Will load named templates from resources. If the argument "t" is `nil`, it is
+created from the first resource.
+
+### Resource Interface
+
+#### func Open() (io.Reader, error)
+Returns a reader to resource data
+
+#### func Size() int64
+Returns resource size (to be used with `Content-Length` HTTP header).
+
+#### func ModTime() time.Time
+Returns modification time (to be used with `Last-Modified` HTTP header).
+
+### Example Code
+
+    package main
+
+    import (
+            "fmt"
+            "net/http"
+            "os"
+
+            "bitbucket.org/tebeka/nrsc"
+    )
+
+    func indexHandler(w http.ResponseWriter, req *http.Request) {
+            fmt.Fprintf(w, "Hello World\n")
+    }
+
+    func main() {
+            nrsc.Handle("/static/")
+            http.HandleFunc("/", indexHandler)
+            if err := http.ListenAndServe(":8080", nil); err != nil {
+                    fmt.Fprintf(os.Stderr, "error: %s\n", err)
+                    os.Exit(1)
+            }
+    }
+
+
+## Contact
+https://bitbucket.org/tebeka/nrsc
+    
+## License
+MIT (see [LICENSE.txt][license])
+
+[license]: https://bitbucket.org/tebeka/nrsc/src/tip/LICENSE.txt

README.rst

-`nrsc` - Resource Compiler for Go
-=================================
-
-`nrsc`  a directory of resource into a Go source file so you can still
-deploy a single executable as a web server with all the CSS, image files, JS ...
-included.
-
-
-Installing
-==========
-::
-
-    go get bitbucket.org/tebeka/nrsc
-
-Also grab the `nrsc` script from here_
-
-And you'll need `zip` somewhere in your path.
-
-.. _here: http://bit.ly/nrsc-script
-
-Invocation
-==========
-::
-
-    go build
-    nrsc <executable> <resource dir> [zip options]
-
-
-API
-===
-The `nrsc` package has the following interface
-
-`nrsc.Handle(prefix string)`
-    This will register with the `net/http` module to handle all paths starting with prefix. 
-
-    When a request is handled, `prefix` is stripped and then a resource is
-    located and served.
-
-    Resource that are not found will cause an HTTP 404 response.
-    
-
-`nrsc.Get(path string) Resource`
-    Will return a resource interface (or `nil` if not found) (see resource interface below).
-    This allows you more control on how to serve.
-
-
-`LoadTemplates(t *template.Template, filenames ...string) (*template.Template, error)`
-    Will load named templates from resources. If the argument "t" is `nil`, it is
-    created from the first resource.
-
-Resource Interface
-------------------
-
-`func Open() (io.Reader, error)`
-    Returns a reader to resource data
-
-`func Size() int64`
-    Returns resource size (to be used with `Content-Length` HTTP header).
-
-`func ModTime() time.Time`
-    Returns modification time (to be used with `Last-Modified` HTTP header).
-
-
-Example Code
-------------
-::
-
-    package main
-
-    import (
-            "fmt"
-            "net/http"
-            "os"
-
-            "bitbucket.org/tebeka/nrsc"
-    )
-
-    func indexHandler(w http.ResponseWriter, req *http.Request) {
-            fmt.Fprintf(w, "Hello World\n")
-    }
-
-    func main() {
-            nrsc.Handle("/static/")
-            http.HandleFunc("/", indexHandler)
-            if err := http.ListenAndServe(":8080", nil); err != nil {
-                    fmt.Fprintf(os.Stderr, "error: %s\n", err)
-                    os.Exit(1)
-            }
-    }
-
-Contact
-=======
-https://bitbucket.org/tebeka/nrsc
-    
-License
-=======
-MIT (see `LICENSE.txt`_)
-
-.. _`LICENSE.txt`: https://bitbucket.org/tebeka/nrsc/src/tip/LICENSE.txt
 Making it happen:
 	1. Add code to serve resources (see example below)
 	2. Compile your executable
-	3. Run the `nrsc` script from https://bitbucket.org/tebeka/nrsc/src
+	3. Run "nrsc /path/to/exe /path/to/resources"
 	4. Deploy
 
 Example code:

nrsc

-#!/bin/bash
-# Pack assets as zip payload in go executable
-
-# Idea from Carlos Castillo (http://bit.ly/SmYXXm)
-
-case "$1" in
-    -h | --help )
-        echo "usage: $(basename $0) EXECTABLE RESOURCE_DIR [ZIP OPTIONS]";
-        exit;;
-    --version )
-        echo "nrsc version 0.3.1"; exit;;
-esac
-
-if [ $# -lt 2 ]; then
-    $0 -h
-    exit 1
-fi
-
-exe=$1
-shift
-root=$1
-shift
-
-if [ ! -f "${exe}" ]; then
-    echo "error: can't find $exe"
-    exit 1
-fi
-
-if [ ! -d "${root}" ]; then
-    echo "error: ${root} is not a directory"
-    exit 1
-fi
-
-# Exit on 1'st error
-set -e
-
-# The below is due to the retarded OSX mktemp
-tmp=$(mktemp)
-tmpzip=${tmp}.zip
-trap "rm -f ${tmp} ${tmpzip}" EXIT
-
-# Create zip file
-(cd "${root}" && zip -r "${tmpzip}" . $@)
-
-# Append zip to executable
-cat "${tmpzip}" >> "${exe}"
-# Fix zip offset in file
-zip -q -A "${exe}"
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"time"
+)
+
+const (
+	Version = "0.4.0"
+)
+
+func die(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format, args...)
+	os.Exit(1)
+}
+
+func exists(path string, dir bool) bool {
+	fi, err := os.Stat(path)
+	if err != nil {
+		return false
+	}
+
+	if dir {
+		return fi.IsDir()
+	} else {
+		return !fi.IsDir()
+	}
+}
+
+func tmpZip() string {
+	return fmt.Sprintf("%s/nrsc-%d.zip", os.TempDir(), time.Now().Unix())
+}
+
+func mkzip(root, zip string, zipArgs []string) error {
+	pwd, err := os.Getwd()
+	if err != nil {
+		return err
+	}
+
+	if err = os.Chdir(root); err != nil {
+		return err
+	}
+	defer os.Chdir(pwd)
+
+	args := []string{"-r", zip, "."}
+	args = append(args, zipArgs...)
+
+	cmd := exec.Command("zip", args...)
+	if err = cmd.Run(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func appendFile(dest, src string) error {
+	in, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer in.Close()
+
+	out, err := os.OpenFile(dest, os.O_RDWR, 0)
+	if err != nil {
+		return err
+	}
+	out.Seek(0, os.SEEK_END)
+	defer out.Close()
+
+	_, err = io.Copy(out, in)
+	return err
+}
+
+
+func fixZipOffset(exe string) error {
+	return exec.Command("zip", "-q", "-A", exe).Run()
+}
+
+func main() {
+	flag.Usage = func() {
+        fmt.Println("usage: nrsc EXECTABLE RESOURCE_DIR [ZIP OPTIONS]")
+	}
+
+	version := flag.Bool("version", false, "show version and exit")
+	flag.Parse()
+
+	if *version {
+		fmt.Printf("nrsc version %s\n", Version)
+	}
+
+	if flag.NArg() < 2 {
+		die("error: Wrong number of arguments\n")
+	}
+
+	exe := flag.Arg(0)
+	if !exists(exe, false) {
+		die("error: `%s` is not a file", exe)
+	}
+
+	root := flag.Arg(1)
+	if !exists(root, true) {
+		die("error: `%s` is not a directory", root)
+	}
+
+	zip := tmpZip()
+	defer os.Remove(zip)
+
+	if err := mkzip(root, zip, flag.Args()[2:]); err != nil {
+		die("error: can't create zip - %s", err)
+	}
+
+	if err := appendFile(exe, zip); err != nil {
+		die("error: can't append zip to %s - %s", exe, err)
+	}
+
+	if err := fixZipOffset(exe); err != nil {
+		die("error: can't fix zip offset in %s - %s", exe, err)
+	}
+}
 )
 
 const (
-	root = "/tmp/nrsc-test"
 	port = 9888
 )
 
+var root string
+
+func init() {
+	root = testDir()
+}
+
+func testDir() string {
+	host, err := os.Hostname()
+	if err != nil {
+		host = "localhost"
+	}
+	return fmt.Sprintf("%s/nrsc-test-%s-%s", os.TempDir(), os.Getenv("USER"), host)
+}
+
 func TestMask(t *testing.T) {
 	resp := getResp(t, "/static/i.gif")
 	if resp.StatusCode != http.StatusUnauthorized {
 	return nil
 }
 
-func fixGOPATH(cwd string) {
-	os.Setenv("GOPATH", fmt.Sprintf("%s/../..", cwd))
-}
-
 func init() {
 	if err := initDir(); err != nil {
 		panic(err)
 	path := func(name string) string {
 		return fmt.Sprintf("%s/%s", cwd, name)
 	}
-	fixGOPATH(cwd)
+
+	os.Chdir("nrsc")
+	cmd := exec.Command("go", "build")
+	if err := cmd.Run(); err != nil {
+		fmt.Printf("error building nrsc: %s\n", err)
+		panic(err)
+	}
 
 	os.Chdir(root)
 	defer os.Chdir(cwd)
 
-	cmd := exec.Command("go", "install", "nrsc")
-	if err := cmd.Run(); err != nil {
-		fmt.Printf("error installing: %s\n", err)
-		panic(err)
-	}
+	testExe := "nrsc-test"
 
-	cmd = exec.Command("go", "build")
+	cmd = exec.Command("go", "build", "-o", testExe)
 	if err := cmd.Run(); err != nil {
 		fmt.Printf("error building: %s\n", err)
 		panic(err)
 	}
 
-	cmd = exec.Command(path("nrsc"), "nrsc-test", path("test-resources"))
+	cmd = exec.Command(path("nrsc/nrsc"), testExe, path("test-resources"))
 	if err := cmd.Run(); err != nil {
 		fmt.Printf("error packing: %s\n", err)
 		panic(err)
 	"os"
 	"regexp"
 
-	"nrsc"
+	"bitbucket.org/tebeka/nrsc"
 )
 
 type params struct {