Externalizing web assets
mostly to ease editing, but also for practice with file manipulation, gzipping, base64'ing, etc.
This commit is contained in:
parent
4c44252a49
commit
f86855d22d
1
conway/.gitignore
vendored
Normal file
1
conway/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/web_assets/web_assets
|
@ -4,18 +4,21 @@ TARGETS := \
|
|||||||
$(LIBS) \
|
$(LIBS) \
|
||||||
github.com/meatballhat/box-o-sand/conway/conways-game-of-life
|
github.com/meatballhat/box-o-sand/conway/conways-game-of-life
|
||||||
|
|
||||||
all: deps build
|
all: test
|
||||||
|
|
||||||
build: test
|
test: build
|
||||||
|
go test -x -v -test.parallel=4 $(LIBS)
|
||||||
|
|
||||||
|
build: web_assets.go deps
|
||||||
go install -x $(TARGETS)
|
go install -x $(TARGETS)
|
||||||
|
|
||||||
|
web_assets.go: $(wildcard web_assets/*.*)
|
||||||
|
./gen-web-assets
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
go clean -x -i $(TARGETS)
|
go clean -x -i $(TARGETS)
|
||||||
|
|
||||||
test:
|
|
||||||
go test -x -v -test.parallel=4 $(LIBS)
|
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
go get -x $(TARGETS)
|
go get -n -x $(TARGETS)
|
||||||
|
|
||||||
.PHONY: all build clean deps test
|
.PHONY: all build clean deps test
|
||||||
|
@ -477,7 +477,7 @@ func TestHandleWebGameRootReturnsIndexPageForGET(t *testing.T) {
|
|||||||
HandleWebGameRoot(w, req)
|
HandleWebGameRoot(w, req)
|
||||||
|
|
||||||
if w.Status != http.StatusOK {
|
if w.Status != http.StatusOK {
|
||||||
t.Fail()
|
t.Errorf("response status != %d: %d", http.StatusOK, w.Status)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,7 +488,7 @@ func TestHandleWebGameRootReturnsIndexPageForGET(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !matched {
|
if !matched {
|
||||||
t.Fail()
|
t.Errorf("Body did not match expected <h1> element")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
conway/gen-web-assets
Executable file
6
conway/gen-web-assets
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd web_assets
|
||||||
|
go build .
|
||||||
|
./web_assets index.html | gofmt > ../web_assets.go
|
@ -2,6 +2,7 @@ package conway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -28,15 +29,56 @@ type WebGameState struct {
|
|||||||
Img string `json:"i"`
|
Img string `json:"i"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var jQueryMinJs = []byte("")
|
type webAssetStrings struct {
|
||||||
|
IndexHTML []byte
|
||||||
|
NormalizeCSS []byte
|
||||||
|
JqueryMinJS []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var WebAssets webAssetStrings
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
jqmin, err := base64.StdEncoding.DecodeString(JQUERY_MIN_JS)
|
jqmin, err := fromGzB64(JQUERY_MIN_JS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to decode internal jquery.min.js")
|
log.Fatal("Failed to decode internal jquery.min.js")
|
||||||
}
|
}
|
||||||
jQueryMinJs = append(jQueryMinJs, jqmin...)
|
WebAssets.JqueryMinJS = jqmin
|
||||||
|
|
||||||
|
normCss, err := fromGzB64(NORMALIZE_CSS)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to decode internal normalize.css")
|
||||||
|
}
|
||||||
|
WebAssets.NormalizeCSS = normCss
|
||||||
|
|
||||||
|
idxHt, err := fromGzB64(GAME_OF_LIFE_INDEX_HTML)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to decode internal index.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
WebAssets.IndexHTML = idxHt
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromGzB64(input string) ([]byte, error) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(input)
|
||||||
|
if err != nil {
|
||||||
|
return []byte(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(decoded)
|
||||||
|
zReader, err := gzip.NewReader(buf)
|
||||||
|
if err != nil {
|
||||||
|
return []byte(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer zReader.Close()
|
||||||
|
|
||||||
|
gunzipped, err := ioutil.ReadAll(zReader)
|
||||||
|
if err != nil {
|
||||||
|
return []byte(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gunzipped, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWebGameParams(q url.Values) *WebGameParams {
|
func NewWebGameParams(q url.Values) *WebGameParams {
|
||||||
@ -99,7 +141,7 @@ func HandleWebGameRoot(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(GAME_OF_LIFE_INDEX_HTML))
|
w.Write(WebAssets.IndexHTML)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,14 +154,14 @@ func HandleWebGameStatic(w http.ResponseWriter, req *http.Request) {
|
|||||||
if req.URL.Path == "/static/normalize.css" {
|
if req.URL.Path == "/static/normalize.css" {
|
||||||
w.Header().Set("Content-Type", "text/css")
|
w.Header().Set("Content-Type", "text/css")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(NORMALIZE_CSS))
|
w.Write(WebAssets.NormalizeCSS)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.URL.Path == "/static/jquery.min.js" {
|
if req.URL.Path == "/static/jquery.min.js" {
|
||||||
w.Header().Set("Content-Type", "text/javascript")
|
w.Header().Set("Content-Type", "text/javascript")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(jQueryMinJs)
|
w.Write(WebAssets.JqueryMinJS)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1860
conway/web_assets.go
1860
conway/web_assets.go
File diff suppressed because one or more lines are too long
110
conway/web_assets/gen.go
Normal file
110
conway/web_assets/gen.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
webAssetsTmpl = template.Must(template.New("web_assets").Parse("" +
|
||||||
|
"package conway\n" +
|
||||||
|
"\n" +
|
||||||
|
"const (\n" +
|
||||||
|
" GAME_OF_LIFE_INDEX_HTML = `{{.IndexHTML}}`\n" +
|
||||||
|
" NORMALIZE_CSS = `{{.NormalizeCSS}}`\n" +
|
||||||
|
" JQUERY_MIN_JS = `{{.JqueryMinJS}}`\n" +
|
||||||
|
")\n"))
|
||||||
|
normalizeCssUrl = "http://necolas.github.com/normalize.css/2.0.1/normalize.css"
|
||||||
|
jqueryMinJsUrl = "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"
|
||||||
|
)
|
||||||
|
|
||||||
|
type webAssetStrings struct {
|
||||||
|
IndexHTML string
|
||||||
|
NormalizeCSS string
|
||||||
|
JqueryMinJS string
|
||||||
|
Now time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func die(err error) {
|
||||||
|
log.Fatal("CRUD:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toB64(input []byte) string {
|
||||||
|
return base64.StdEncoding.EncodeToString(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGz(input []byte) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
zwriter := gzip.NewWriter(&buf)
|
||||||
|
|
||||||
|
zbuf := bufio.NewWriter(zwriter)
|
||||||
|
|
||||||
|
toWrite := len(input)
|
||||||
|
for written := 0; written < toWrite; {
|
||||||
|
n, err := zbuf.Write(input[written:])
|
||||||
|
if err != nil {
|
||||||
|
die(err)
|
||||||
|
}
|
||||||
|
written += n
|
||||||
|
}
|
||||||
|
|
||||||
|
zbuf.Flush()
|
||||||
|
zwriter.Close()
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchUrl(url string) []byte {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
die(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
die(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchIndexHtml(indexHtmlPath string) []byte {
|
||||||
|
fd, err := os.Open(indexHtmlPath)
|
||||||
|
if err != nil {
|
||||||
|
die(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(fd)
|
||||||
|
if err != nil {
|
||||||
|
die(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
die(errors.New(fmt.Sprintf("Usage: %s <index.html-path>", os.Args[0])))
|
||||||
|
}
|
||||||
|
|
||||||
|
assets := &webAssetStrings{
|
||||||
|
IndexHTML: toB64(toGz(fetchIndexHtml(os.Args[1]))),
|
||||||
|
NormalizeCSS: toB64(toGz(fetchUrl(normalizeCssUrl))),
|
||||||
|
JqueryMinJS: toB64(toGz(fetchUrl(jqueryMinJsUrl))),
|
||||||
|
Now: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
webAssetsTmpl.Execute(os.Stdout, assets)
|
||||||
|
}
|
51
conway/web_assets/index.html
Normal file
51
conway/web_assets/index.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Conway's Game of Life</title>
|
||||||
|
<link type="text/css" rel="stylesheet" href="/static/normalize.css" />
|
||||||
|
<script type="text/javascript" src="/static/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
curState = {};
|
||||||
|
curImg = "";
|
||||||
|
|
||||||
|
function populateInitialState() {
|
||||||
|
$.getJSON('/state', {}, onPopulateResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextState(state) {
|
||||||
|
$.post('/state', JSON.stringify({s: state}, null, 2), onPopulateResponse, 'json');
|
||||||
|
}
|
||||||
|
|
||||||
|
function playGame() {
|
||||||
|
goToNextState(curState);
|
||||||
|
setTimeout(playGame, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPopulateResponse(data, textStatus, jqXHR) {
|
||||||
|
$.extend(curState, data.s);
|
||||||
|
curImg = data.i;
|
||||||
|
var $stateImg = $('#state_img');
|
||||||
|
if ($stateImg.length < 1) {
|
||||||
|
$stateImg = $('<img id="state_img" />').appendTo($('#state_container'));
|
||||||
|
}
|
||||||
|
$stateImg.attr('src', 'data:image/png;base64,' + encodeURI(data.i));
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
populateInitialState();
|
||||||
|
$('#step').click(function() { goToNextState(curState); });
|
||||||
|
$('#play').click(playGame);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Conway's Game of Life</h1>
|
||||||
|
<div id="controls">
|
||||||
|
<button id="step">Step</button>
|
||||||
|
<button id="play">Play</button>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div id="state_container">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user