Got basis for web done
This commit is contained in:
parent
acfa4ff7fe
commit
7d2fa0a6ef
261
bindata.go
Normal file
261
bindata.go
Normal file
@ -0,0 +1,261 @@
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// web/css/pure-min.css
|
||||
// web/index.html
|
||||
// web/js/index.js
|
||||
// web/js/ractive.min.js
|
||||
// DO NOT EDIT!
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// bindataRead reads the given file from disk. It returns an error on failure.
|
||||
func bindataRead(path, name string) ([]byte, error) {
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset %s at %s: %v", name, path, err)
|
||||
}
|
||||
return buf, err
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
// webCssPureMinCss reads file data from disk. It returns an error on failure.
|
||||
func webCssPureMinCss() (*asset, error) {
|
||||
path := "/home/tshannon/workspace/go/src/git.townsourced.com/ironsmith/web/css/pure-min.css"
|
||||
name := "web/css/pure-min.css"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// webIndexHtml reads file data from disk. It returns an error on failure.
|
||||
func webIndexHtml() (*asset, error) {
|
||||
path := "/home/tshannon/workspace/go/src/git.townsourced.com/ironsmith/web/index.html"
|
||||
name := "web/index.html"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// webJsIndexJs reads file data from disk. It returns an error on failure.
|
||||
func webJsIndexJs() (*asset, error) {
|
||||
path := "/home/tshannon/workspace/go/src/git.townsourced.com/ironsmith/web/js/index.js"
|
||||
name := "web/js/index.js"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// webJsRactiveMinJs reads file data from disk. It returns an error on failure.
|
||||
func webJsRactiveMinJs() (*asset, error) {
|
||||
path := "/home/tshannon/workspace/go/src/git.townsourced.com/ironsmith/web/js/ractive.min.js"
|
||||
name := "web/js/ractive.min.js"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"web/css/pure-min.css": webCssPureMinCss,
|
||||
"web/index.html": webIndexHtml,
|
||||
"web/js/index.js": webJsIndexJs,
|
||||
"web/js/ractive.min.js": webJsRactiveMinJs,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"web": &bintree{nil, map[string]*bintree{
|
||||
"css": &bintree{nil, map[string]*bintree{
|
||||
"pure-min.css": &bintree{webCssPureMinCss, map[string]*bintree{}},
|
||||
}},
|
||||
"index.html": &bintree{webIndexHtml, map[string]*bintree{}},
|
||||
"js": &bintree{nil, map[string]*bintree{
|
||||
"index.js": &bintree{webJsIndexJs, map[string]*bintree{}},
|
||||
"ractive.min.js": &bintree{webJsRactiveMinJs, map[string]*bintree{}},
|
||||
}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
17
cycle.go
17
cycle.go
@ -32,12 +32,6 @@ func (p *Project) errHandled(err error) bool {
|
||||
return true
|
||||
}
|
||||
defer func() {
|
||||
err = p.ds.Close()
|
||||
if err != nil {
|
||||
log.Printf("Error closing the datastore for project %s: %s\n", p.id(), err)
|
||||
}
|
||||
p.ds = nil
|
||||
|
||||
//clean up version folder if it exists
|
||||
|
||||
if p.version != "" {
|
||||
@ -156,6 +150,11 @@ func (p *Project) load() {
|
||||
|
||||
p.fetch()
|
||||
|
||||
if p.errHandled(p.ds.Close()) {
|
||||
return
|
||||
}
|
||||
p.ds = nil
|
||||
|
||||
//full cycle completed
|
||||
|
||||
if p.poll > 0 {
|
||||
@ -197,7 +196,7 @@ func (p *Project) fetch() {
|
||||
return
|
||||
}
|
||||
|
||||
if p.version == lVer {
|
||||
if p.version == "" || p.version == lVer {
|
||||
// no new build clean up temp dir
|
||||
p.errHandled(os.RemoveAll(tempDir))
|
||||
|
||||
@ -302,9 +301,5 @@ func (p *Project) release() {
|
||||
//build successfull, remove working dir
|
||||
p.errHandled(os.RemoveAll(p.workingDir()))
|
||||
|
||||
if p.errHandled(p.ds.Close()) {
|
||||
return
|
||||
}
|
||||
|
||||
vlog("Project: %s Version %s built, tested, and released successfully.\n", p.id(), p.version)
|
||||
}
|
||||
|
139
error.go
Normal file
139
error.go
Normal file
@ -0,0 +1,139 @@
|
||||
// Copyright 2016 Tim Shannon. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
acceptHTML = "text/html"
|
||||
)
|
||||
|
||||
// Err404 is a standard 404 error response
|
||||
var Err404 = errors.New("Resource not found")
|
||||
|
||||
func errHandled(err error, w http.ResponseWriter) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var status, errMsg string
|
||||
|
||||
errMsg = err.Error()
|
||||
|
||||
switch err.(type) {
|
||||
|
||||
case *Fail:
|
||||
status = statusFail
|
||||
case *http.ProtocolError, *json.SyntaxError, *json.UnmarshalTypeError:
|
||||
//Hardcoded external errors which can bubble up to the end users
|
||||
// without exposing internal server information, make them failures
|
||||
err = FailFromErr(err)
|
||||
status = statusFail
|
||||
|
||||
errMsg = fmt.Sprintf("We had trouble parsing your input, please check your input and try again: %s", err)
|
||||
default:
|
||||
status = statusError
|
||||
errMsg = "An internal server error has occurred"
|
||||
}
|
||||
|
||||
if status == statusFail {
|
||||
respondJsendCode(w, &JSend{
|
||||
Status: status,
|
||||
Message: errMsg,
|
||||
Data: err.(*Fail).Data,
|
||||
}, err.(*Fail).HTTPStatus)
|
||||
} else {
|
||||
respondJsend(w, &JSend{
|
||||
Status: status,
|
||||
Message: errMsg,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// four04 is a standard 404 response if request header accepts text/html
|
||||
// they'll get a 404 page, otherwise a json response
|
||||
func four04(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
response := &JSend{
|
||||
Status: statusFail,
|
||||
Message: "Resource not found",
|
||||
Data: r.URL.String(),
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
||||
result, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
log.Printf("Error marshalling 404 response: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = w.Write(result)
|
||||
if err != nil {
|
||||
log.Printf("Error in four04: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Fail is an error whose contents can be exposed to the client and is usually the result
|
||||
// of incorrect client input
|
||||
type Fail struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
HTTPStatus int `json:"-"` //gets set in the error response
|
||||
}
|
||||
|
||||
func (f *Fail) Error() string {
|
||||
return f.Message
|
||||
}
|
||||
|
||||
// NewFail creates a new failure, data is optional
|
||||
func NewFail(message string, data ...interface{}) error {
|
||||
return &Fail{
|
||||
Message: message,
|
||||
Data: data,
|
||||
HTTPStatus: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// FailFromErr returns a new failure based on the passed in error, data is optional
|
||||
// if passed in error is nil, then nil is returned
|
||||
func FailFromErr(err error, data ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return NewFail(err.Error(), data...)
|
||||
}
|
||||
|
||||
// IsEqual tests whether an error is equal to another error / failure
|
||||
func (f *Fail) IsEqual(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return err.Error() == f.Error()
|
||||
}
|
||||
|
||||
// IsFail tests whether the passed in error is a failure
|
||||
func IsFail(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
switch err.(type) {
|
||||
case *Fail:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
100
json.go
Normal file
100
json.go
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright 2016 Tim Shannon. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
statusSuccess = "success"
|
||||
statusError = "error"
|
||||
statusFail = "fail"
|
||||
)
|
||||
|
||||
const maxJSONSize = 1 << 20 //10MB
|
||||
|
||||
var errInputTooLarge = &Fail{
|
||||
Message: "Input size is too large, please check your input and try again",
|
||||
HTTPStatus: http.StatusRequestEntityTooLarge,
|
||||
}
|
||||
|
||||
// JSend is the standard format for a response from townsourced
|
||||
type JSend struct {
|
||||
Status string `json:"status"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Failures []error `json:"failures,omitempty"`
|
||||
More bool `json:"more,omitempty"` // more data exists for this request
|
||||
}
|
||||
|
||||
//respondJsend marshalls the input into a json byte array
|
||||
// and writes it to the reponse with appropriate header
|
||||
func respondJsend(w http.ResponseWriter, response *JSend) {
|
||||
respondJsendCode(w, response, 0)
|
||||
}
|
||||
|
||||
// respondJsendCode is the same as respondJSend, but lets you specify a status code
|
||||
func respondJsendCode(w http.ResponseWriter, response *JSend, statusCode int) {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if len(response.Failures) > 0 && response.Message == "" {
|
||||
response.Message = "One or more item has failed. Check the individual failures for details."
|
||||
}
|
||||
|
||||
result, err := json.MarshalIndent(response, "", " ")
|
||||
if err != nil {
|
||||
log.Printf("Error marshalling response: %s", err)
|
||||
|
||||
result, _ = json.Marshal(&JSend{
|
||||
Status: statusError,
|
||||
Message: "An internal error occurred, and we'll look into it.",
|
||||
})
|
||||
}
|
||||
|
||||
if statusCode <= 0 {
|
||||
switch response.Status {
|
||||
case statusFail:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case statusError:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
//default is status 200
|
||||
} else {
|
||||
w.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
_, err = w.Write(result)
|
||||
if err != nil {
|
||||
log.Printf("Error writing jsend response: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseInput(r *http.Request, result interface{}) error {
|
||||
lr := &io.LimitedReader{R: r.Body, N: maxJSONSize + 1}
|
||||
buff, err := ioutil.ReadAll(lr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if lr.N == 0 {
|
||||
return errInputTooLarge
|
||||
}
|
||||
|
||||
if len(buff) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = json.Unmarshal(buff, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -197,9 +197,11 @@ func (p *projectList) closeAll() {
|
||||
defer p.RUnlock()
|
||||
|
||||
for i := range p.data {
|
||||
if p.data[i].ds != nil {
|
||||
_ = p.data[i].ds.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// startProjectLoader polls for new projects
|
||||
func startProjectLoader() {
|
||||
|
47
server.go
47
server.go
@ -4,7 +4,12 @@
|
||||
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var webRoot *http.ServeMux
|
||||
|
||||
@ -71,7 +76,47 @@ func (m *methodHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Routes
|
||||
/project/<project-id>/<version>/<stage>
|
||||
|
||||
/project/ - list all projects
|
||||
/project/<project-id> - list all versions in a project, triggers new builds
|
||||
/project/<project-id>/<version> - list combined output of all stages
|
||||
/project/<project-id>/<version>/<stage. - list output of a given stage of a given version
|
||||
|
||||
*/
|
||||
|
||||
func routes() {
|
||||
webRoot = http.NewServeMux()
|
||||
|
||||
webRoot.Handle("/", &methodHandler{
|
||||
get: rootGet,
|
||||
})
|
||||
|
||||
webRoot.Handle("/project/", &methodHandler{
|
||||
get: projectGet,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func rootGet(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
//send index.html
|
||||
serveAsset(w, r, "web/index.html")
|
||||
return
|
||||
}
|
||||
|
||||
serveAsset(w, r, path.Join("web", r.URL.Path))
|
||||
}
|
||||
|
||||
func serveAsset(w http.ResponseWriter, r *http.Request, asset string) {
|
||||
data, err := Asset(asset)
|
||||
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeContent(w, r, r.URL.Path, time.Time{}, bytes.NewReader(data))
|
||||
}
|
||||
|
11
web/css/pure-min.css
vendored
Normal file
11
web/css/pure-min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
23
web/index.html
Normal file
23
web/index.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Ironsmith - A simple, script driven continuous integration tool">
|
||||
|
||||
<title>Ironsmith - A simple, script driven continuous integration tool</title>
|
||||
|
||||
<link rel="stylesheet" href="/css/pure-min.css">
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script id="tMain" type="text/ractive">
|
||||
|
||||
</script>
|
||||
<script src="/js/ractive.min.js"></script>
|
||||
<script src="/js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
19
web/js/index.js
Normal file
19
web/js/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2016 Tim Shannon. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
/* jshint strict: true */
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
var r = new Ractive({
|
||||
el: "body",
|
||||
template: "#tMain",
|
||||
data: function() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
})();
|
9
web/js/ractive.min.js
vendored
Normal file
9
web/js/ractive.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
40
webProject.go
Normal file
40
webProject.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2016 Tim Shannon. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// /project/<project-id>/<version>/<stage>
|
||||
func splitPath(path string) (project, version, stage string) {
|
||||
s := strings.Split(path, "/")
|
||||
if len(s) < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
project, _ = url.QueryUnescape(s[2])
|
||||
if len(s) < 4 {
|
||||
return
|
||||
}
|
||||
|
||||
version, _ = url.QueryUnescape(s[3])
|
||||
|
||||
if len(s) < 5 {
|
||||
return
|
||||
}
|
||||
stage, _ = url.QueryUnescape(s[4])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func projectGet(w http.ResponseWriter, r *http.Request) {
|
||||
prj, ver, stg := splitPath(r.URL.Path)
|
||||
|
||||
fmt.Printf("Project: %s Version: %s Stage: %s\n", prj, ver, stg)
|
||||
}
|
Loading…
Reference in New Issue
Block a user