From d4f7dc57fcb4c1bc2f1a95076c43f325af861db8 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Fri, 6 Feb 2015 22:03:05 -0800 Subject: [PATCH] first commit way too late. --- .gitignore | 30 + .travis.yml | 8 + LICENSE | 22 + README.md | 5 + api.go | 144 ++ api_test.go | 115 ++ apiutil.go | 23 + apiutil_test.go | 37 + dbconnector.go | 52 + dbconnector_test.go | 21 + doormen/doormen.go | 118 ++ doormen/doormen_test.go | 124 ++ .../inmemorydbconnectior_test.go | 106 ++ inmemoryconnector/inmemorydbconnector.go | 83 + logpublisher/logpublisher.go | 26 + logpublisher/logpublisher_test.go | 5 + mongoconnector/mongo.go | 25 + mongoconnector/mongoconnector.go | 71 + nanomsgpublisher/nanomsgpublisher.go | 41 + nsqpublisher/nsqpublisher.go | 38 + params.yml | 45 + publisher.go | 52 + publisher_test.go | 7 + server.go | 33 + shared/interfaces.go | 5 + shared/params.go | 100 ++ shared/params_test.go | 142 ++ shared/util.go | 12 + static/.gitignore | 7 + static/bower.json | 26 + static/coffee/app.coffee | 35 + static/coffee/controllers/apispecCtrl.coffee | 28 + static/coffee/controllers/doormanCtrl.coffee | 30 + .../controllers/doormanErrorCtrl.coffee | 6 + static/coffee/controllers/doormenCtrl.coffee | 9 + static/coffee/controllers/homeCtrl.coffee | 6 + .../coffee/controllers/newdoormanCtrl.coffee | 57 + static/coffee/controllers/sideBarCtrl.coffee | 6 + static/coffee/main.coffee | 20 + static/gulpfile.js | 50 + static/img/.gitkeep | 0 static/index.html | 44 + static/package.json | 14 + static/partials/apispec.html | 46 + static/partials/doorman.html | 29 + static/partials/doormanError.html | 6 + static/partials/doormen.html | 13 + static/partials/home.html | 7 + static/partials/newdoorman.html | 54 + static/scss/_doorman.scss | 21 + static/scss/_settings.scss | 1453 +++++++++++++++++ static/scss/style.scss | 41 + static_server.go | 20 + 53 files changed, 3518 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 api.go create mode 100644 api_test.go create mode 100644 apiutil.go create mode 100644 apiutil_test.go create mode 100644 dbconnector.go create mode 100644 dbconnector_test.go create mode 100644 doormen/doormen.go create mode 100644 doormen/doormen_test.go create mode 100644 inmemoryconnector/inmemorydbconnectior_test.go create mode 100644 inmemoryconnector/inmemorydbconnector.go create mode 100644 logpublisher/logpublisher.go create mode 100644 logpublisher/logpublisher_test.go create mode 100644 mongoconnector/mongo.go create mode 100644 mongoconnector/mongoconnector.go create mode 100644 nanomsgpublisher/nanomsgpublisher.go create mode 100644 nsqpublisher/nsqpublisher.go create mode 100644 params.yml create mode 100644 publisher.go create mode 100644 publisher_test.go create mode 100644 server.go create mode 100644 shared/interfaces.go create mode 100644 shared/params.go create mode 100644 shared/params_test.go create mode 100644 shared/util.go create mode 100644 static/.gitignore create mode 100644 static/bower.json create mode 100644 static/coffee/app.coffee create mode 100644 static/coffee/controllers/apispecCtrl.coffee create mode 100644 static/coffee/controllers/doormanCtrl.coffee create mode 100644 static/coffee/controllers/doormanErrorCtrl.coffee create mode 100644 static/coffee/controllers/doormenCtrl.coffee create mode 100644 static/coffee/controllers/homeCtrl.coffee create mode 100644 static/coffee/controllers/newdoormanCtrl.coffee create mode 100644 static/coffee/controllers/sideBarCtrl.coffee create mode 100644 static/coffee/main.coffee create mode 100644 static/gulpfile.js create mode 100755 static/img/.gitkeep create mode 100644 static/index.html create mode 100644 static/package.json create mode 100644 static/partials/apispec.html create mode 100644 static/partials/doorman.html create mode 100644 static/partials/doormanError.html create mode 100644 static/partials/doormen.html create mode 100644 static/partials/home.html create mode 100644 static/partials/newdoorman.html create mode 100644 static/scss/_doorman.scss create mode 100644 static/scss/_settings.scss create mode 100644 static/scss/style.scss create mode 100644 static_server.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c47440d --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + + +*.out +/static/js +/static/css/style.css +/doormanserver diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..78b420f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +--- + +language: go + +go: + - 1.3 + - 1.4 + - tip diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..44bcb32 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 didier amyot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a21bd81 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Doorman server [![Build Status](https://travis-ci.org/didiercrunch/doormanserver.svg)](https://travis-ci.org/didiercrunch/doormanserver) + + +This is the server of the doorman project. It is the centralized user interface +where you control the configuration of your A/B tests. diff --git a/api.go b/api.go new file mode 100644 index 0000000..9373ea1 --- /dev/null +++ b/api.go @@ -0,0 +1,144 @@ +package main + +import ( + "fmt" + "net/http" + + "encoding/json" + "github.com/didiercrunch/doormanserver/doormen" + "github.com/gorilla/mux" + "gopkg.in/mgo.v2/bson" +) + +func getDoormenIdsAsJson(id *doormen.DoormanId) string { + return fmt.Sprintf(`{"name": "%v", "id": "%v", "url": "/api/doormen/%v"}`, id.Name, id.Id.Hex(), id.Id.Hex()) +} + +func CreateDoorman(w http.ResponseWriter, request *http.Request) { + wdef := new(doormen.DoormanDefinition) + defer request.Body.Close() + decoder := json.NewDecoder(request.Body) + if err := decoder.Decode(wdef); err != nil { + w.WriteHeader(400) + fmt.Fprintln(w, "cannot create doorman definition \n", err) + return + } + wdef.Id = bson.NewObjectId() + if err := wdef.Validate(); err != nil { + w.WriteHeader(400) + fmt.Fprintln(w, "New doorman is invalid \n", err) + return + } + if err := CreateDoormanInDatabase(wdef); err != nil { + w.WriteHeader(500) + fmt.Fprintln(w, "Server error \n", err) + return + } + go publisher.Emit(wdef.Id, wdef) + w.Header().Set("location", "/api/doormen/"+wdef.Id.Hex()) + w.WriteHeader(201) +} + +func GetAllDoormen(w http.ResponseWriter, request *http.Request) { + fmt.Fprint(w, `{"doormen" :[`) + doormenIds := conn.GetAllDoormen() + buff := <-doormenIds + if buff == nil { + fmt.Fprint(w, "]}") + return + } + for doormanId := range doormenIds { + fmt.Fprint(w, getDoormenIdsAsJson(buff)+`,`) + buff = doormanId + } + fmt.Fprint(w, getDoormenIdsAsJson(buff), "]}") +} + +func getDoormanIdFromRequest(w http.ResponseWriter, request *http.Request) bson.ObjectId { + id := mux.Vars(request)["id"] + if !bson.IsObjectIdHex(id) { + return "" + } else { + return bson.ObjectIdHex(id) + } +} + +func GetDoorman(w http.ResponseWriter, r *http.Request) { + id := getDoormanIdFromRequest(w, r) + if id == "" { + Write404Error(w) + return + } + doorman, err := conn.GetDoorman(id) + if err != nil { + Write500Error(w, err) + } else if doorman == nil { + Write404Error(w) + } else { + encoder := json.NewEncoder(w) + encoder.Encode(doorman) + } +} + +func UpdateDoorman(w http.ResponseWriter, request *http.Request) { + id := getDoormanIdFromRequest(w, request) + if id == "" || !conn.ExistsId(id) { + Write404Error(w) + return + } + + wdef := new(doormen.DoormanDefinition) + defer request.Body.Close() + decoder := json.NewDecoder(request.Body) + if err := decoder.Decode(wdef); err != nil { + Write400Error(w, err) + return + } + wdef.Id = id + + if err := wdef.Validate(); err != nil { + Write400Error(w, err) + return + } + + originalDoorman, err := conn.GetDoorman(id) + if err != nil { + Write500Error(w, err) + return + } + if err := originalDoorman.CanBeUpdatedBy(wdef); err != nil { + Write400Error(w, err) + return + } + + if err := UpdateDoormanInDatabase(wdef); err != nil { + Write500Error(w, err) + return + } + go publisher.Emit(wdef.Id, wdef) + w.WriteHeader(200) + +} + +type Api struct { + GetAllDoormen http.HandlerFunc + CreateDoorman http.HandlerFunc + GetDoorman http.HandlerFunc + UpdateDoorman http.HandlerFunc +} + +func (a *Api) ServeApi(router *mux.Router) { + router.Path("/doormen").Methods("GET").HandlerFunc(a.GetAllDoormen) + router.Path("/doormen").Methods("POST").HandlerFunc(a.CreateDoorman) + router.Path("/doormen/{id}").Methods("GET").HandlerFunc(a.GetDoorman) + router.Path("/doormen/{id}").Methods("PUT").HandlerFunc(a.UpdateDoorman) +} + +func serveApi(router *mux.Router) { + api := new(Api) + api.CreateDoorman = CreateDoorman + api.GetAllDoormen = GetAllDoormen + api.GetDoorman = GetDoorman + api.UpdateDoorman = UpdateDoorman + api.ServeApi(router) +} diff --git a/api_test.go b/api_test.go new file mode 100644 index 0000000..7774b3b --- /dev/null +++ b/api_test.go @@ -0,0 +1,115 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/didiercrunch/doormanserver/inmemoryconnector" + "github.com/didiercrunch/doormanserver/doormen" + "github.com/gorilla/mux" +) + +func init() { + conn = inmemoryconnector.NewInMemoryDBConnector() +} + +func OkHandler(w http.ResponseWriter, request *http.Request) { + fmt.Fprintf(w, "ok") +} + +func assertBodyIs(t *testing.T, res *http.Response, expt string) { + if body, err := ioutil.ReadAll(res.Body); err != nil { + t.Error(err) + } else if s := string(body); s != expt { + t.Error("expected ", expt, " but received ", s) + } +} + +func TestPOSTToDoormen(t *testing.T) { + api := &Api{CreateDoorman: OkHandler} + r := mux.NewRouter() + api.ServeApi(r) + + ts := httptest.NewServer(r) + defer ts.Close() + res, err := http.Post(ts.URL+"/doormen", "", bytes.NewBufferString("boby")) + if err != nil { + t.Error(err) + return + } + assertBodyIs(t, res, "ok") +} + +func TestGETToDoormen(t *testing.T) { + api := &Api{GetAllDoormen: OkHandler} + + r := mux.NewRouter() + api.ServeApi(r) + + ts := httptest.NewServer(r) + defer ts.Close() + res, err := http.Get(ts.URL + "/doormen") + if err != nil { + t.Error(err) + return + } + assertBodyIs(t, res, "ok") +} + +func TestGetAllDoormenEmpty(t *testing.T) { + conn = inmemoryconnector.NewInMemoryDBConnector() + api := &Api{GetAllDoormen: GetAllDoormen} + + r := mux.NewRouter() + api.ServeApi(r) + + ts := httptest.NewServer(r) + defer ts.Close() + + res, err := http.Get(ts.URL + "/doormen") + if err != nil { + t.Error(err) + return + } + + if body, err := ioutil.ReadAll(res.Body); err != nil { + t.Error(err) + } else if s := string(body); s != `{"doormen" :[]}` { + t.Error(s) + } +} + +func TestGetAllDoormen(t *testing.T) { + conn = inmemoryconnector.NewInMemoryDBConnector() + conn.Save(doormen.QuickNewDoormanDefinition("foo", 0.25, 0.75)) + conn.Save(doormen.QuickNewDoormanDefinition("bar", 0.5, 0.5)) + + api := &Api{GetAllDoormen: GetAllDoormen} + r := mux.NewRouter() + api.ServeApi(r) + + ts := httptest.NewServer(r) + defer ts.Close() + + res, err := http.Get(ts.URL + "/doormen") + if err != nil { + t.Error(err) + return + } + d := json.NewDecoder(res.Body) + m := make(map[string][]*doormen.DoormanDefinition) + + if err := d.Decode(&m); err != nil { + t.Error(err) + return + } + if len(m["doormen"]) != 2 { + t.Error() + } + +} diff --git a/apiutil.go b/apiutil.go new file mode 100644 index 0000000..7f269d9 --- /dev/null +++ b/apiutil.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "log" + "net/http" +) + +func Write500Error(w http.ResponseWriter, err error) { + w.WriteHeader(500) + fmt.Fprint(w, "server error") + log.Println(err) +} + +func Write404Error(w http.ResponseWriter) { + w.WriteHeader(404) + fmt.Fprint(w, "url not found") +} + +func Write400Error(w http.ResponseWriter, err error) { + w.WriteHeader(400) + fmt.Fprint(w, err.Error()) +} diff --git a/apiutil_test.go b/apiutil_test.go new file mode 100644 index 0000000..720bef5 --- /dev/null +++ b/apiutil_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "errors" + "net/http/httptest" + "testing" +) + +func TestWrite404Error(t *testing.T) { + rw := httptest.NewRecorder() + Write404Error(rw) + if rw.Code != 404 { + t.Error() + } +} + +func TestWrite500Error(t *testing.T) { + rw := httptest.NewRecorder() + Write500Error(rw, errors.New("blah")) + if rw.Code != 500 { + t.Error() + } + if string(rw.Body.Bytes()) != "server error" { + t.Error() + } +} + +func TestWrite400Error(t *testing.T) { + rw := httptest.NewRecorder() + Write400Error(rw, errors.New("blah")) + if rw.Code != 400 { + t.Error() + } + if string(rw.Body.Bytes()) != "blah" { + t.Error() + } +} diff --git a/dbconnector.go b/dbconnector.go new file mode 100644 index 0000000..1a9794d --- /dev/null +++ b/dbconnector.go @@ -0,0 +1,52 @@ +package main + +import ( + "errors" + "log" + + "github.com/didiercrunch/doormanserver/inmemoryconnector" + "github.com/didiercrunch/doormanserver/mongoconnector" + "github.com/didiercrunch/doormanserver/shared" + "github.com/didiercrunch/doormanserver/doormen" + "gopkg.in/mgo.v2/bson" +) + +type DBConnector interface { + Save(wab *doormen.DoormanDefinition) error + Update(wab *doormen.DoormanDefinition) error + Exists(wab *doormen.DoormanDefinition) bool + ExistsId(id bson.ObjectId) bool + GetAllDoormen() <-chan *doormen.DoormanId + GetDoorman(id bson.ObjectId) (*doormen.DoormanDefinition, error) + DeleteDoorman(id bson.ObjectId) error +} + +var conn DBConnector + +func initMongoDbConnector() { + var err error + if conn, err = mongoconnector.NewMongoDBConnector(); err != nil { + panic(err) + } +} + +func init() { + if params := shared.GetParams(); params.UseMongoDb() { + initMongoDbConnector() + log.Println("using mongodb as database") + } else { + conn = inmemoryconnector.NewInMemoryDBConnector() + log.Println("using inmemory as database") + } +} + +func CreateDoormanInDatabase(wab *doormen.DoormanDefinition) error { + if conn.Exists(wab) { + return errors.New("doormen already exists") + } + return conn.Save(wab) +} + +func UpdateDoormanInDatabase(wab *doormen.DoormanDefinition) error { + return conn.Update(wab) +} diff --git a/dbconnector_test.go b/dbconnector_test.go new file mode 100644 index 0000000..4fac5aa --- /dev/null +++ b/dbconnector_test.go @@ -0,0 +1,21 @@ +package main + +import ( + "testing" + + "github.com/didiercrunch/doormanserver/inmemoryconnector" + "github.com/didiercrunch/doormanserver/doormen" +) + +func TestCreateDoorman(t *testing.T) { + wab := doormen.NewDoormanDefinition("some_name") + conn = inmemoryconnector.NewInMemoryDBConnector() + + if err := CreateDoormanInDatabase(wab); err != nil { + t.Error(err) + } + + if err := CreateDoormanInDatabase(wab); err == nil { + t.Error("should have an error here") + } +} diff --git a/doormen/doormen.go b/doormen/doormen.go new file mode 100644 index 0000000..be92d9f --- /dev/null +++ b/doormen/doormen.go @@ -0,0 +1,118 @@ +package doormen + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "time" + + "github.com/didiercrunch/doorman/shared" + "gopkg.in/mgo.v2/bson" +) + +const epsilon float64 = 0.0001 + +type DoormanValue struct { + Name string `json:"name" bson:"name"` + Probability float64 `json:"probability" bson:"probability"` +} + +type DoormanId struct { + Id bson.ObjectId `json:"id,omitempty" bson:"_id"` + Name string `json:"name" bson:"name"` +} + +func (wId *DoormanId) AsJson() string { + if wId.Id == "" { + return `{}` + } + return fmt.Sprintf(`{"%v":"%v"}`, wId.Id.Hex(), wId.Name) +} + +type DoormanDefinition struct { + Id bson.ObjectId `json:"id,omitempty" bson:"_id"` + Name string `json:"name" bson:"name"` + Values []*DoormanValue `json:"values" bson:"values"` +} + +func NewDoormanDefinition(name string) *DoormanDefinition { + return &DoormanDefinition{Id: bson.NewObjectId(), Name: name} +} + +// a way to quickly create doormen in tests. do not use in real code. will +// panic if a doorman is wrongly specify +func QuickNewDoormanDefinition(name string, probs ...float64) *DoormanDefinition { + ret := NewDoormanDefinition(name) + for i, prob := range probs { + ret.Values = append(ret.Values, &DoormanValue{"T" + strconv.Itoa(i), prob}) + } + if err := ret.Validate(); err != nil { + panic(err) + } + return ret +} + +func (dmd *DoormanDefinition) ValidateDoormanValueNames() error { + m := make(map[string]bool) + for _, w := range dmd.Values { + if name := w.Name; name == "" { + return errors.New("doorman value cannot be empty") + } else { + m[name] = true + } + } + if len(m) != len(dmd.Values) { + return errors.New("doorman value names must be unique within a doorman") + } + return nil +} + +func (dmd *DoormanDefinition) ValidateDoormanValueProbabilities() error { + var sum float64 = 0 + for _, w := range dmd.Values { + if p := w.Probability; p < 0-epsilon || p > 1+epsilon { + return errors.New(fmt.Sprintf("doorman value %v is out of range", p)) + } else { + sum += p + } + } + if sum < 0-epsilon || sum > 1+epsilon { + return errors.New("the sum of the probability must be 1.0") + } + return nil +} + +func (dmd *DoormanDefinition) Validate() (err error) { + switch { + case dmd.Name == "": + err = errors.New("name cannot be empty") + case len(dmd.Values) < 2: + err = errors.New("need at least 2 values") + case dmd.ValidateDoormanValueNames() != nil: + err = dmd.ValidateDoormanValueNames() + case dmd.ValidateDoormanValueProbabilities() != nil: + err = dmd.ValidateDoormanValueProbabilities() + } + return +} + +func (dmd *DoormanDefinition) CanBeUpdatedBy(dmd2 *DoormanDefinition) error { + if dmd.Name != dmd2.Name { + return errors.New("cannot change doorman name") + } + if len(dmd.Values) != len(dmd2.Values) { + return errors.New("cannot change the number of cases in doorman") + } + return nil +} + +func (dmd *DoormanDefinition) AsDoormanUpdatePayload() ([]byte, error) { + wu := &shared.DoormanUpdater{Id: dmd.Id.Hex()} + wu.Timestamp = time.Now().Unix() + wu.Probabilities = make([]float64, len(dmd.Values)) + for i, value := range dmd.Values { + wu.Probabilities[i] = value.Probability + } + return json.Marshal(wu) +} diff --git a/doormen/doormen_test.go b/doormen/doormen_test.go new file mode 100644 index 0000000..68be1b5 --- /dev/null +++ b/doormen/doormen_test.go @@ -0,0 +1,124 @@ +package doormen + +import ( + "encoding/json" + "math" + "testing" + + "github.com/didiercrunch/doorman/shared" + "gopkg.in/mgo.v2/bson" +) + +func TestNewDoormanDefinition(t *testing.T) { + dmd := NewDoormanDefinition("some_name") + if dmd.Id == "" { + t.Error("need id at all time") + } + if dmd.Name != "some_name" { + t.Error("bad name") + } +} + +func TestValidate(t *testing.T) { + dmd := &DoormanDefinition{Name: ""} + if err := dmd.Validate(); err.Error() != "name cannot be empty" { + t.Error(err) + } + dmd = &DoormanDefinition{Name: "foo"} + if err := dmd.Validate(); err.Error() != "need at least 2 values" { + t.Error(err) + } + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{nil}} + if err := dmd.Validate(); err.Error() != "need at least 2 values" { + t.Error(err) + } + + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"foo", 0}}} + if err := dmd.Validate(); err.Error() != "doorman value names must be unique within a doorman" { + t.Error(err) + } + + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"", 0}}} + if err := dmd.Validate(); err.Error() != "doorman value cannot be empty" { + t.Error(err) + } + + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", -0.25}, &DoormanValue{"bar", 0.5}}} + if err := dmd.Validate(); err.Error() != "doorman value -0.25 is out of range" { + t.Error(err) + } + + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 2.5}, &DoormanValue{"bar", 0.5}}} + if err := dmd.Validate(); err.Error() != "doorman value 2.5 is out of range" { + t.Error(err) + } + + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.75}, &DoormanValue{"bar", 0.5}}} + if err := dmd.Validate(); err.Error() != "the sum of the probability must be 1.0" { + t.Error(err) + } +} + +func TestAsJson(t *testing.T) { + id := &DoormanId{} + if `{}` != id.AsJson() { + t.Fail() + } + id.Name = "bob" + if `{}` != id.AsJson() { + t.Fail() + } + + id.Id = bson.ObjectIdHex("54af464af6fb5e20c4000003") + if `{"54af464af6fb5e20c4000003":"bob"}` != id.AsJson() { + t.Error(id.AsJson()) + } +} + +func TestAsDoormanUpdatePayload(t *testing.T) { + wd := &DoormanDefinition{} + wd.Id = bson.NewObjectId() + wd.Values = []*DoormanValue{&DoormanValue{"C", 0.25}, &DoormanValue{"T1", 0.75}} + data, err := wd.AsDoormanUpdatePayload() + if err != nil { + t.Error(err) + return + } + wu := &shared.DoormanUpdater{} + if err := json.Unmarshal(data, wu); err != nil { + t.Error(err) + } + if wd.Id.Hex() != wu.Id { + t.Error("bad id", wu.Id) + } + if len(wu.Probabilities) != 2 { + t.Error("bad probability length") + } else if math.Abs(wu.Probabilities[0]-0.25) > epsilon { + t.Error("bad first probability") + } else if math.Abs(wu.Probabilities[1]-0.75) > epsilon { + t.Error("bad second probability") + } else if wu.Timestamp == 0 { + t.Error("no time specify") + } else if wu.Timestamp < 1421537442 { + t.Error("timestamp looks weird") + } +} + +func TestQuickNewDoormanDefinition(t *testing.T) { + dmd := QuickNewDoormanDefinition("foo", 0.75, 0.25) + if dmd.Name != "foo" { + t.Error() + } + + if len(dmd.Values) != 2 { + t.Error() + } + + if dmd.Values[0].Name != "T0" || dmd.Values[0].Probability != 0.75 { + t.Error() + } + + if dmd.Values[1].Name != "T1" || dmd.Values[1].Probability != 0.25 { + t.Error() + } +} diff --git a/inmemoryconnector/inmemorydbconnectior_test.go b/inmemoryconnector/inmemorydbconnectior_test.go new file mode 100644 index 0000000..3a2dab4 --- /dev/null +++ b/inmemoryconnector/inmemorydbconnectior_test.go @@ -0,0 +1,106 @@ +package inmemoryconnector + +import ( + "testing" + + "github.com/didiercrunch/doormanserver/doormen" + "gopkg.in/mgo.v2/bson" +) + +func createMockDoorman(name string) *doormen.DoormanDefinition { + return doormen.QuickNewDoormanDefinition(name, 0.5, 0.5) +} + +func TestNewInMemoryDBConnector(t *testing.T) { + c := NewInMemoryDBConnector() + if len(c.memory) != 0 { + t.Error("length at this point should be 0") + } +} + +func TestSave(t *testing.T) { + c := NewInMemoryDBConnector() + if err := c.Save(nil); err != nil { + t.Error(err) + } + if len(c.memory) != 1 { + t.Fail() + } +} + +func TestDeleteDoorman(t *testing.T) { + c := NewInMemoryDBConnector() + c.Save(createMockDoorman("a")) + c.Save(createMockDoorman("b")) + c.Save(createMockDoorman("c")) + + if err := c.DeleteDoorman(c.memory[1].Id); err != nil { + t.Error() + } + if c.memory[0].Name != "a" || c.memory[1].Name != "c" { + t.Error() + } + + if err := c.DeleteDoorman(c.memory[1].Id); err != nil { + t.Error() + } + if c.memory[0].Name != "a" { + t.Error() + } + + if err := c.DeleteDoorman(c.memory[0].Id); err != nil { + t.Error() + } + if len(c.memory) != 0 { + t.Error() + } +} + +func TestGetAllDoormen(t *testing.T) { + c := NewInMemoryDBConnector() + c.Save(createMockDoorman("a")) + c.Save(createMockDoorman("b")) + c.Save(createMockDoorman("c")) + + i := 0 + expt := []string{"a", "b", "c"} + for w := range c.GetAllDoormen() { + if w.Name != expt[i] { + t.Error(i) + } + i++ + } + +} + +func TestGetDoorman(t *testing.T) { + c := NewInMemoryDBConnector() + wab := createMockDoorman("a") + c.Save(wab) + + if w, err := c.GetDoorman(wab.Id); err != nil { + t.Error(err) + } else if w.Name != "a" { + t.Error() + } + + if w, err := c.GetDoorman(createMockDoorman("b").Id); err != nil { + t.Error("missing doorman is not an error") + } else if w != nil { + t.Error("should be nil") + } +} + +func TestExistsId(t *testing.T) { + c := NewInMemoryDBConnector() + w := createMockDoorman("a") + c.Save(w) + + if !c.ExistsId(w.Id) { + t.Error() + } + if c.ExistsId(bson.NewObjectId()) { + t.Error() + } + +} diff --git a/inmemoryconnector/inmemorydbconnector.go b/inmemoryconnector/inmemorydbconnector.go new file mode 100644 index 0000000..4a91ee7 --- /dev/null +++ b/inmemoryconnector/inmemorydbconnector.go @@ -0,0 +1,83 @@ +package inmemoryconnector + +import ( + "errors" + + "github.com/didiercrunch/doormanserver/doormen" + "gopkg.in/mgo.v2/bson" +) + +type InMemoryDBConnector struct { + memory []*doormen.DoormanDefinition +} + +func NewInMemoryDBConnector() *InMemoryDBConnector { + return &InMemoryDBConnector{make([]*doormen.DoormanDefinition, 0)} +} + +func (c *InMemoryDBConnector) Save(dmd *doormen.DoormanDefinition) error { + c.memory = append(c.memory, dmd) + return nil +} + +func (c *InMemoryDBConnector) Exists(dmd *doormen.DoormanDefinition) bool { + for _, w := range c.memory { + if w.Name == dmd.Name { + return true + } + } + return false +} + +func (c *InMemoryDBConnector) ExistsId(id bson.ObjectId) bool { + for _, w := range c.memory { + if w.Id == id { + return true + } + } + return false + +} + +func (c *InMemoryDBConnector) GetAllDoormen() <-chan *doormen.DoormanId { + ch := make(chan *doormen.DoormanId) + go func() { + for _, s := range c.memory { + ch <- &doormen.DoormanId{Name: s.Name, Id: s.Id} + } + close(ch) + }() + return ch + +} + +func (c *InMemoryDBConnector) GetDoorman(id bson.ObjectId) (*doormen.DoormanDefinition, error) { + for _, doorman := range c.memory { + if doorman.Id == id { + return doorman, nil + } + } + return nil, nil +} + +func (c *InMemoryDBConnector) DeleteDoorman(id bson.ObjectId) error { + toRemove := -1 + for i, doorman := range c.memory { + if doorman.Id == id { + toRemove = i + break + } + } + c.memory = append(c.memory[0:toRemove], c.memory[toRemove+1:len(c.memory)]...) + return nil +} + +func (c *InMemoryDBConnector) Update(dmd *doormen.DoormanDefinition) error { + for i, w := range c.memory { + if w.Id == dmd.Id { + c.memory[i] = dmd + return nil + } + } + return errors.New("nothing to update") +} diff --git a/logpublisher/logpublisher.go b/logpublisher/logpublisher.go new file mode 100644 index 0000000..6b45104 --- /dev/null +++ b/logpublisher/logpublisher.go @@ -0,0 +1,26 @@ +package logpublisher + +import ( + "log" + + "github.com/didiercrunch/doormanserver/shared" + "gopkg.in/mgo.v2/bson" +) + +type LogPublisher struct { + Printer func(v ...interface{}) +} + +func (pub *LogPublisher) Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error { + if data, err := doorman.AsDoormanUpdatePayload(); err != nil { + return err + } else { + pub.Printer(doormanId.Hex(), " : ", string(data)) + return nil + } +} + +func (pub *LogPublisher) Init() error { + pub.Printer = log.Println + return nil +} diff --git a/logpublisher/logpublisher_test.go b/logpublisher/logpublisher_test.go new file mode 100644 index 0000000..d91e56e --- /dev/null +++ b/logpublisher/logpublisher_test.go @@ -0,0 +1,5 @@ +package logpublisher + +import "testing" + +func TestLogPublisher(t *testing.T) {} diff --git a/mongoconnector/mongo.go b/mongoconnector/mongo.go new file mode 100644 index 0000000..dd938c1 --- /dev/null +++ b/mongoconnector/mongo.go @@ -0,0 +1,25 @@ +package mongoconnector + +//var mongoClient int + +import ( + "github.com/didiercrunch/doormanserver/shared" + mgo "gopkg.in/mgo.v2" +) + +var mongoClient *mgo.Session +var DB *mgo.Database + +func initMongo() error { + if mongoClient != nil { + return nil + } + + var err error + mongoClient, err = mgo.Dial(shared.GetParams().Mongo.Url) + if err != nil { + return err + } + DB = mongoClient.DB(shared.GetParams().Mongo.DatabaseName) + return nil +} diff --git a/mongoconnector/mongoconnector.go b/mongoconnector/mongoconnector.go new file mode 100644 index 0000000..2db00f0 --- /dev/null +++ b/mongoconnector/mongoconnector.go @@ -0,0 +1,71 @@ +package mongoconnector + +import ( + "github.com/didiercrunch/doormanserver/shared" + "github.com/didiercrunch/doormanserver/doormen" + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +type MongoDBConnector struct { + Collection *mgo.Collection +} + +func NewMongoDBConnector() (*MongoDBConnector, error) { + if err := initMongo(); err != nil { + return nil, err + } + + c := new(MongoDBConnector) + params := shared.GetParams() + c.Collection = DB.C(params.Mongo.DoormanDefinitionCollection) + err := c.Collection.EnsureIndex(mgo.Index{Key: []string{"name"}, Unique: true}) + return c, err +} + +func (c *MongoDBConnector) Exists(wab *doormen.DoormanDefinition) bool { + err := c.Collection.Find(bson.M{"name": wab.Name}).One(&struct{}{}) + return err != mgo.ErrNotFound +} + +func (c *MongoDBConnector) ExistsId(id bson.ObjectId) bool { + err := c.Collection.FindId(id).One(&struct{}{}) + return err != mgo.ErrNotFound +} + +func (c *MongoDBConnector) Save(wab *doormen.DoormanDefinition) error { + return c.Collection.Insert(wab) +} + +func (c *MongoDBConnector) GetAllDoormen() <-chan *doormen.DoormanId { + ch := make(chan *doormen.DoormanId) + go func() { + iter := c.Collection.Find(nil).Iter() + + for res := new(doormen.DoormanId); iter.Next(res); res = new(doormen.DoormanId) { + ch <- res + } + if err := iter.Err(); err != nil { + ch <- &doormen.DoormanId{Id: "", Name: err.Error()} + } + close(ch) + }() + return ch +} + +func (c *MongoDBConnector) GetDoorman(id bson.ObjectId) (*doormen.DoormanDefinition, error) { + doorman := new(doormen.DoormanDefinition) + if err := c.Collection.FindId(id).One(doorman); err == mgo.ErrNotFound { + return nil, nil + } else { + return doorman, err + } +} + +func (c *MongoDBConnector) DeleteDoorman(id bson.ObjectId) error { + return c.Collection.RemoveId(id) +} + +func (c *MongoDBConnector) Update(wab *doormen.DoormanDefinition) error { + return c.Collection.Update(&bson.M{"_id": wab.Id}, &bson.M{"$set": wab}) +} diff --git a/nanomsgpublisher/nanomsgpublisher.go b/nanomsgpublisher/nanomsgpublisher.go new file mode 100644 index 0000000..7681b98 --- /dev/null +++ b/nanomsgpublisher/nanomsgpublisher.go @@ -0,0 +1,41 @@ +package nanomsgpublisher + +import ( + "errors" + "log" + + "github.com/didiercrunch/doormanserver/shared" + "github.com/gdamore/mangos" + "github.com/gdamore/mangos/protocol/pub" + "github.com/gdamore/mangos/transport/ipc" + "github.com/gdamore/mangos/transport/tcp" + "gopkg.in/mgo.v2/bson" +) + +type NanoMsgPublisher struct { + Url string + sock mangos.Socket +} + +func (p *NanoMsgPublisher) Init() error { + var err error + if p.sock, err = pub.NewSocket(); err != nil { + return errors.New("can't listen on pub socket:" + err.Error()) + } + p.sock.AddTransport(ipc.NewTransport()) + p.sock.AddTransport(tcp.NewTransport()) + if err = p.sock.Listen(shared.GetParams().NanoMsg.NanoMsgUrl); err != nil { + return errors.New("can't listen on pub socket: %s" + err.Error()) + } + return nil + +} + +func (p *NanoMsgPublisher) Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error { + log.Println("Emit for ", doormanId.Hex()) + if d, err := doorman.AsDoormanUpdatePayload(); err != nil { + return err + } else { + return p.sock.Send(d) + } +} diff --git a/nsqpublisher/nsqpublisher.go b/nsqpublisher/nsqpublisher.go new file mode 100644 index 0000000..c3b06f5 --- /dev/null +++ b/nsqpublisher/nsqpublisher.go @@ -0,0 +1,38 @@ +package nsqpublisher + +import ( + "github.com/bitly/go-nsq" + "github.com/didiercrunch/doormanserver/shared" + "gopkg.in/mgo.v2/bson" +) + +type NsqPublisher struct { + Config *nsq.Config + Producer *nsq.Producer +} + +func (pub *NsqPublisher) InitConfig() { + pub.Config = nsq.NewConfig() +} + +func (pub *NsqPublisher) Init() error { + var err error + pub.InitConfig() + pub.Producer, err = nsq.NewProducer(shared.GetParams().NSQ.NSQLookupdUrl, pub.Config) + return err + +} + +func (pub *NsqPublisher) Stop() error { + pub.Producer.Stop() + return nil +} + +func (pub *NsqPublisher) Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error { + topic := doormanId.Hex() + if data, err := doorman.AsDoormanUpdatePayload(); err != nil { + return err + } else { + return pub.Producer.Publish(topic, data) + } +} diff --git a/params.yml b/params.yml new file mode 100644 index 0000000..72eab02 --- /dev/null +++ b/params.yml @@ -0,0 +1,45 @@ +--- + + +# This is union web site params' file. The file is in yaml format. The file +# should be auto-documented + + +# the port where the union web site will run +port: 1999 + +# the host name. 0.0.0.0 to share the service with everyone on the network. +# localhost for running on localhost without the possibility to share the +# service +host: 0.0.0.0 #localhost + +# determine which message queue to use +message_queue: nanomsg # nanomsg # nsq + +# determine which database to use +database: in_memory # mongo + +################################################################################ + + +# all the parameters relative to nsq message-queue. To use nsq as message +# queue you need to speficy "nsq" in the "message_queue" key at top level. +nsq: + nsq_lookupd_url: 127.0.0.1:4150 + + +# all the parameters related to mongodb. To use mongodb as your database you +# need to specify "mongodb" in the "database" key at top level +mongo: + # the url where to get mongodb + url: localhost:27017 + + # the name of the database to use + database_name: doorman + + # the name of the collection where all the street can be found + doorman_definition_collection: doorman_definitions + +# all the parameters relative to nanomsg message-queue +nanomsg: + nanomsg_url: tcp://127.0.0.1:40899 diff --git a/publisher.go b/publisher.go new file mode 100644 index 0000000..a3bd64e --- /dev/null +++ b/publisher.go @@ -0,0 +1,52 @@ +package main + +import ( + "log" + + "github.com/didiercrunch/doormanserver/logpublisher" + "github.com/didiercrunch/doormanserver/nanomsgpublisher" + "github.com/didiercrunch/doormanserver/nsqpublisher" + "github.com/didiercrunch/doormanserver/shared" + "gopkg.in/mgo.v2/bson" +) + +type Publisher interface { + Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error + Init() error +} + +var publisher Publisher + +func initNSQPublisher() { + publisher = new(nsqpublisher.NsqPublisher) + if err := publisher.Init(); err != nil { + log.Panic(err) + return + } +} + +func initNanoMsgPublisher() { + publisher = new(nanomsgpublisher.NanoMsgPublisher) + if err := publisher.Init(); err != nil { + log.Panic(err) + return + } +} + +func initPublisher(params *shared.Params) { + if params.UseNSQ() { + initNSQPublisher() + log.Println("using nsq as message queue") + + } else if params.UseNanomsg() { + initNanoMsgPublisher() + log.Println("using nanomsg as message queue") + } else { + publisher = &logpublisher.LogPublisher{log.Println} + log.Println("using stdout as message queue") + } + +} +func init() { + initPublisher(shared.GetParams()) +} diff --git a/publisher_test.go b/publisher_test.go new file mode 100644 index 0000000..08107a0 --- /dev/null +++ b/publisher_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func TestInitPublisher(t *testing.T) { + +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..c4cae82 --- /dev/null +++ b/server.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "strconv" + + "github.com/didiercrunch/doormanserver/shared" + "github.com/gorilla/mux" +) + +func createMuxRouter() http.Handler { + r := mux.NewRouter() + serveApi(r.PathPrefix("/api").Subrouter()) + serveStatic(r.PathPrefix("/").Subrouter()) + return r +} + +func GetAddressToServe() string { + params := shared.GetParams() + return params.Host + ":" + strconv.Itoa(params.Port) +} + +func main() { + r := createMuxRouter() + http.Handle("/", r) + address := GetAddressToServe() + fmt.Println("server running at", address) + if err := http.ListenAndServe(address, createMuxRouter()); err != nil { + log.Fatal(err) + } +} diff --git a/shared/interfaces.go b/shared/interfaces.go new file mode 100644 index 0000000..6cdcf4a --- /dev/null +++ b/shared/interfaces.go @@ -0,0 +1,5 @@ +package shared + +type AsDoormanUpdatePayloader interface { + AsDoormanUpdatePayload() ([]byte, error) +} diff --git a/shared/params.go b/shared/params.go new file mode 100644 index 0000000..3bc99a6 --- /dev/null +++ b/shared/params.go @@ -0,0 +1,100 @@ +package shared + +import ( + "io/ioutil" + "log" + "strings" + + goyaml "gopkg.in/yaml.v2" +) + +var PARAM_FILE string = "params.yml" + +var params *Params + +type MongoParams struct { + Url string `yaml:"url,omitempty"` + DatabaseName string `yaml:"database_name,omitempty"` + DoormanDefinitionCollection string `yaml:"doorman_definition_collection,omitempty"` +} + +type NSQParams struct { + NSQLookupdUrl string `yaml:"nsq_lookupd_url,omitempty"` +} + +type NanoMsgParams struct { + NanoMsgUrl string `yaml:"nanomsg_url,omitempty"` +} + +type Params struct { + Port int `yaml:"port,omitempty"` + Host string `yaml:"host,omitempty"` + Mongo *MongoParams `yaml:"mongo,omitempty"` + NSQ *NSQParams `yaml:"nsq,omitempty"` + NanoMsg *NanoMsgParams `yaml:"nanomsg,omitempty"` + + Database string `yaml:"database,omitempty"` + MessageQueue string `yaml:"message_queue,omitempty"` +} + +func (p *Params) UseMongoDb() bool { + db := strings.ToLower(p.Database) + return db == "mongo" || db == "mongodb" +} + +func (p *Params) UseNSQ() bool { + m := strings.ToLower(p.MessageQueue) + return m == "nsq" +} + +func (p *Params) UseNanomsg() bool { + m := strings.ToLower(p.MessageQueue) + return m == "nanomsg" +} + +func (p *Params) InsertDefaultValues() *Params { + if p.Host == "" { + p.Host = "localhost" + } + if p.Port == 0 { + p.Port = 1999 + } + if p.Mongo == nil { + p.Mongo = new(MongoParams) + } + if p.NSQ == nil { + p.NSQ = new(NSQParams) + } + return p +} + +func LoadFromYamlFile(fileName string) (*Params, error) { + if !Exists(fileName) { + return new(Params).InsertDefaultValues(), nil + } + data, err := ioutil.ReadFile(fileName) + if err != nil { + return nil, err + } + return LoadFromYamlData(data) +} + +func LoadFromYamlData(data []byte) (*Params, error) { + var err error + p := new(Params) + if err = goyaml.Unmarshal(data, p); err != nil { + return nil, err + } + return p.InsertDefaultValues(), nil +} + +func GetParams() *Params { + return params +} + +func init() { + var err error + if params, err = LoadFromYamlFile(PARAM_FILE); err != nil { + log.Panic(err) + } +} diff --git a/shared/params_test.go b/shared/params_test.go new file mode 100644 index 0000000..29fd0db --- /dev/null +++ b/shared/params_test.go @@ -0,0 +1,142 @@ +package shared + +import ( + "testing" +) + +const yaml_params = ` +--- +port: 9999 +host: 0.0.0.0 #localhost +# all the params relative to mongodb +mongo: + # the url where to get mongodb + url: localhost:1234 + + # the name of the database to use + database_name: doorman + + # the name of the collection where all the street can be found + doorman_definition_collection: doorman_definitions + +# all the parameters relative to nsq message-queue +nsq: + # the url where to find nsq lookupd url + nsq_lookupd_url: 127.0.0.1:4161 + +# all the parameters relative to nanomsg message-queue +nanomsg: + nanomsg_url: ipc:///tmp/doorman.ipc + + +` + +func TestLoadFromYamlData(t *testing.T) { + p, err := LoadFromYamlData([]byte(yaml_params)) + if err != nil { + t.Error(err) + return + } + if p.Port != 9999 { + t.Error("port") + } + if p.Host != "0.0.0.0" { + t.Error("host") + } +} + +func TestMongoParams(t *testing.T) { + p, err := LoadFromYamlData([]byte(yaml_params)) + if err != nil { + t.Error(err) + return + } + if p.Mongo.Url != "localhost:1234" { + t.Error("mongo.url") + } + if p.Mongo.DatabaseName != "doorman" { + t.Error("mongo.DatabaseName") + } + if p.Mongo.DoormanDefinitionCollection != "doorman_definitions" { + t.Error("Mongo.DoormanDefinitionCollection", p.Mongo.DoormanDefinitionCollection) + } +} + +func TestNSQParams(t *testing.T) { + p, err := LoadFromYamlData([]byte(yaml_params)) + if err != nil { + t.Error(err) + return + } + if p.NSQ.NSQLookupdUrl != "127.0.0.1:4161" { + t.Error("Nsq.NSQLookupdUrl", p.NSQ.NSQLookupdUrl) + } +} + +func TestNanoMsgParams(t *testing.T) { + p, err := LoadFromYamlData([]byte(yaml_params)) + if err != nil { + t.Error(err) + return + } + if p.NanoMsg.NanoMsgUrl != "ipc:///tmp/doorman.ipc" { + t.Error("NanoMsgParams.NanoMsgUrl", p.NanoMsg.NanoMsgUrl) + } +} + +func TestMinimalParams(t *testing.T) { + params := "---\n" + p, err := LoadFromYamlData([]byte(params)) + if err != nil { + t.Error(err) + } + if p.Mongo.Url != "" { + t.Error() + } + if p.Port != 1999 { + t.Error("bad default port", p.Port) + } + if p.Host != "localhost" { + t.Error("bad default host") + } + if p.MessageQueue != "" { + t.Error("message queue") + } + + if p.Database != "" { + t.Error("database") + } +} + +func TestUseMongoDb(t *testing.T) { + p := &Params{} + if p.Database = ""; p.UseMongoDb() { + t.Error() + } + if p.Database = "mOnGo"; !p.UseMongoDb() { + t.Error() + } + if p.Database = "monGoDb"; !p.UseMongoDb() { + t.Error() + } +} + +func TestUseNSQ(t *testing.T) { + p := &Params{} + if p.MessageQueue = ""; p.UseNSQ() { + t.Error() + } + if p.MessageQueue = "nSq"; !p.UseNSQ() { + t.Error() + } +} + +func TestUseNanoMsg(t *testing.T) { + p := &Params{} + if p.MessageQueue = ""; p.UseNanomsg() { + t.Error() + } + if p.MessageQueue = "nAnOmSg"; !p.UseNanomsg() { + t.Error() + } +} diff --git a/shared/util.go b/shared/util.go new file mode 100644 index 0000000..1b83ab1 --- /dev/null +++ b/shared/util.go @@ -0,0 +1,12 @@ +package shared + +import "os" + +func Exists(name string) bool { + if _, err := os.Stat(name); err != nil { + if os.IsNotExist(err) { + return false + } + } + return true +} diff --git a/static/.gitignore b/static/.gitignore new file mode 100644 index 0000000..ea220bf --- /dev/null +++ b/static/.gitignore @@ -0,0 +1,7 @@ +logs/* +!.gitkeep +node_modules/ +bower_components/ +tmp +.DS_Store +.idea diff --git a/static/bower.json b/static/bower.json new file mode 100644 index 0000000..2f706a4 --- /dev/null +++ b/static/bower.json @@ -0,0 +1,26 @@ +{ + "name": "angular-requirejs-seed", + "description": "AngularJS seed project modified for requireJS support.", + "version": "0.2.0", + "homepage": "https://github.com/tnajdek/angular-requirejs-seed", + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "angular": "~1.2.1", + "angular-route": "~1.2.1", + "angular-scenario": "~1.2.1", + "angular-mocks": "~1.2.1", + "requirejs": "~2.1.9", + "requirejs-text": "~2.0.10", + "foundation": "zurb/bower-foundation", + "html5-boilerplate": "~4.3.0", + "angularAMD": "~0.2.1", + "angular-foundation": "~0.5.1" + } +} diff --git a/static/coffee/app.coffee b/static/coffee/app.coffee new file mode 100644 index 0000000..cf1a2ea --- /dev/null +++ b/static/coffee/app.coffee @@ -0,0 +1,35 @@ +define [ + "angularAMD" + "angular-route" + "foundation" +], (angularAMD) -> + app = angular.module("webapp", ["ngRoute", "mm.foundation"]) + app.config ($routeProvider) -> + $routeProvider.when("/", angularAMD.route( + templateUrl: "partials/home.html" + controller: "HomeCtrl" + controllerUrl: "controllers/homeCtrl" + )).when("/apispec", angularAMD.route( + templateUrl: "partials/apispec.html" + controller: "ApispecCtrl" + controllerUrl: "controllers/apispecCtrl" + )).when("/newdoorman", angularAMD.route( + templateUrl: "partials/newdoorman.html" + controller: "NewDoormanCtrl" + controllerUrl: "controllers/newdoormanCtrl" + )).when("/doormen", angularAMD.route( + templateUrl: "partials/doormen.html" + controller: "DoormenCtrl" + controllerUrl: "controllers/doormenCtrl" + )).when("/doormen/error/:error/:id", angularAMD.route( + templateUrl: "partials/doormanError.html" + controller: "DoormanErrorCtrl" + controllerUrl: "controllers/doormanErrorCtrl" + )).when("/doormen/:id", angularAMD.route( + templateUrl: "partials/doorman.html" + controller: "DoormanCtrl" + controllerUrl: "controllers/doormanCtrl" + )).otherwise redirectTo: "/" + return + + angularAMD.bootstrap(app) diff --git a/static/coffee/controllers/apispecCtrl.coffee b/static/coffee/controllers/apispecCtrl.coffee new file mode 100644 index 0000000..d3ff7b8 --- /dev/null +++ b/static/coffee/controllers/apispecCtrl.coffee @@ -0,0 +1,28 @@ +define ["app", "angularAMD"], (app, angularAMD) -> + app.controller "ApispecCtrl", ($scope) -> + $scope.methods = + get: "GET" + post: "POST" + + angularAMD.directive "wabMethod", () -> + directive = + restrict: 'E', + scope: + method: '@' + link: (scope, element) -> + scope.methods = + get: "GET" + put: "PUT" + delete: "DELETE" + post: "POST" + + template: '{{ methods[method] }}' + return directive + + angularAMD.directive "wabApi", () -> + directive = + restrict: 'E', + scope: + endpoint: '@' + template: '/api{{ endpoint }}' + return directive diff --git a/static/coffee/controllers/doormanCtrl.coffee b/static/coffee/controllers/doormanCtrl.coffee new file mode 100644 index 0000000..da0c60b --- /dev/null +++ b/static/coffee/controllers/doormanCtrl.coffee @@ -0,0 +1,30 @@ +define ["app"], (app) -> + app.controller "DoormanCtrl", ["$scope", "$http","$routeParams","$location", ($scope, $http, $routeParams, $location) -> + $scope.doorman = {values: []} + $scope.apiurl = "/api/doormen/#{$routeParams.id}" + $http.get($scope.apiurl).then((res) -> + $scope.doorman = res.data + ).catch((res) -> + if res.status == 404 + $location.path("/doormen/error/#{res.status}/#{$routeParams.id}") + ) + + $scope.deleteDoorman = () -> + console.log("not yet implemented") + + $scope.validate = ()-> + ret = 0 + for val in $scope.doorman.values + ret += val.probability + return Math.abs(ret - 1.0) < 0.000001 + + $scope.updateDoorman = () -> + if !$scope.validate() + return + $http.put($scope.apiurl, $scope.doorman).then((ret)-> + console.log "update" + ).catch((res) -> + d = 9 + ) + ] + return diff --git a/static/coffee/controllers/doormanErrorCtrl.coffee b/static/coffee/controllers/doormanErrorCtrl.coffee new file mode 100644 index 0000000..aca9ead --- /dev/null +++ b/static/coffee/controllers/doormanErrorCtrl.coffee @@ -0,0 +1,6 @@ +define ["app"], (app) -> + app.controller "DoormanErrorCtrl", ["$scope","$routeParams", ($scope, $routeParams) -> + $scope.id = $routeParams.id + $scope.error = Number($routeParams.error) + ] + return diff --git a/static/coffee/controllers/doormenCtrl.coffee b/static/coffee/controllers/doormenCtrl.coffee new file mode 100644 index 0000000..0a15622 --- /dev/null +++ b/static/coffee/controllers/doormenCtrl.coffee @@ -0,0 +1,9 @@ +define ["app"], (app) -> + app.controller "DoormenCtrl",[ "$scope", "$http", ($scope, $http) -> + $scope.doormen = [] + $http.get("/api/doormen").then (res) -> + $scope.doormen = res.data.doormen + return + ] + + return diff --git a/static/coffee/controllers/homeCtrl.coffee b/static/coffee/controllers/homeCtrl.coffee new file mode 100644 index 0000000..05b8789 --- /dev/null +++ b/static/coffee/controllers/homeCtrl.coffee @@ -0,0 +1,6 @@ +define ["app"], (app) -> + app.controller "HomeCtrl", ($scope) -> + $scope.message = "message from home ctrl" + return + + return diff --git a/static/coffee/controllers/newdoormanCtrl.coffee b/static/coffee/controllers/newdoormanCtrl.coffee new file mode 100644 index 0000000..ddbf04f --- /dev/null +++ b/static/coffee/controllers/newdoormanCtrl.coffee @@ -0,0 +1,57 @@ +epsilon = 0.0001 + +define ["app"], (app) -> + app.controller "NewDoormanCtrl", ["$scope", "$http", "$location", ($scope, $http, $location) -> + $scope.error = "" + $scope.value = {} + $scope.payload = + name: "" + values: [] + + $scope.isValidValue = () -> + return $scope.value.name and $scope.value.probability + + $scope.addNewValue = () -> + if !$scope.isValidValue() + return + $scope.payload.values.push($scope.value) + $scope.value = {name: "", probability: 0} + + $scope.removeValue = (idx) -> + $scope.payload.values.splice(idx, 1); + + $scope.getProbabilityRemaining = () -> + ret = 0 + for v in $scope.payload.values + ret += v.probability + return 1 - ret + + $scope.redirectToDoorman = (doormanLocation) -> + $http.get(doormanLocation).then (res) -> + id = res.data.id + return $location.url("/doormen/#{id}") + + $scope.createDoorman = () -> + if not $scope.isDoormanValid() + return + $http.post("/api/doormen", $scope.payload). + then((res, s) -> + $scope.redirectToDoorman(res.headers("location")) + ).catch((res) -> + $scope.error = res.data + ) + + $scope.isDoormanValid = () -> + if not $scope.payload.name + return false + if $scope.payload.values.length < 2 + return false + if Math.abs($scope.getProbabilityRemaining()) > epsilon + return false + return true + + + return + ] + + return diff --git a/static/coffee/controllers/sideBarCtrl.coffee b/static/coffee/controllers/sideBarCtrl.coffee new file mode 100644 index 0000000..9b586e6 --- /dev/null +++ b/static/coffee/controllers/sideBarCtrl.coffee @@ -0,0 +1,6 @@ +define ["app"], (app) -> + app.controller "sideBarCtrl", ($scope) -> + $scope.message = "Message from MyCtrl1" + return + + return diff --git a/static/coffee/main.coffee b/static/coffee/main.coffee new file mode 100644 index 0000000..9769510 --- /dev/null +++ b/static/coffee/main.coffee @@ -0,0 +1,20 @@ +require.config + baseUrl: "js" + + # alias libraries paths. Must set 'angular' + paths: + "angular" : "/bower_components/angular/angular.min" + "angular-route": "/bower_components/angular-route/angular-route.min" + "angularAMD" : "/bower_components/angularAMD/angularAMD.min" + "foundation" : "/bower_components/angular-foundation/mm-foundation-tpls" + + + # Add angular modules that does not support AMD out of the box, put it in a shim + shim: + "angularAMD": ["angular"] + "angular-route": ["angular"] + "foundation" : ["angular"] + + + # kick start application + deps: ["app"] diff --git a/static/gulpfile.js b/static/gulpfile.js new file mode 100644 index 0000000..091e32a --- /dev/null +++ b/static/gulpfile.js @@ -0,0 +1,50 @@ +var bower = require('gulp-bower'); +var gulp = require('gulp'); +var coffee = require('gulp-coffee'); +var help = require('gulp-task-listing'); +var clean = require('gulp-clean'); +var sass = require('gulp-sass'); +var rjs = require("gulp-rjs"); + + +gulp.task('help', help); + +gulp.task('bower', function() { + return bower() + .pipe(gulp.dest('bower_components/')) +}); + +gulp.task('sass', ['bower'], function () { + return gulp.src('./scss/*.scss') + .pipe(sass({includePaths: ['bower_components/foundation/scss']})) + .pipe(gulp.dest('./css')); +}); + +gulp.task('coffee', ['bower'], function() { + return gulp.src('coffee/**/*.coffee') + .pipe(coffee({bare: true})) + .pipe(gulp.dest('js/')) +}); + +gulp.task('clean', function(){ + return gulp.src(["js/", "bower_components/", "css/"], {read: false}) + .pipe(clean()) +}); + + +gulp.task('rjs', ['coffee'], function(){ + return gulp.src('coffee/**/*.coffee') + .pipe(coffee()) + .pipe(gulp.dest('./dist')) + .pipe(rjs({baseUrl:'.'})) +}); + + +gulp.task('default', ['bower', 'sass', 'coffee']); + + + +gulp.task('dev', ['bower', 'sass', 'coffee'], function(){ + gulp.watch('coffee/**/*.coffee', ['coffee']); + gulp.watch('./scss/*.scss', ['sass']); +}); diff --git a/static/img/.gitkeep b/static/img/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..0612ffe --- /dev/null +++ b/static/index.html @@ -0,0 +1,44 @@ + + + + + doorman + + + + + +
+
+ + + + + +
+
+
+ + + +
+
+ + + diff --git a/static/package.json b/static/package.json new file mode 100644 index 0000000..1f843fe --- /dev/null +++ b/static/package.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "gulp": "~3.8.9", + "gulp-bower": "0.0.7", + "gulp-clean": "~0.3.1", + "gulp-coffee": "~2.2.0", + "gulp-sass": "^1.2.4", + "gulp-task-listing": "~0.3.0", + "node-sass": "~1.2.3" + }, + "devDependencies": { + "gulp-rjs": "^0.1.0" + } +} diff --git a/static/partials/apispec.html b/static/partials/apispec.html new file mode 100644 index 0000000..d842cb2 --- /dev/null +++ b/static/partials/apispec.html @@ -0,0 +1,46 @@ +
+
+

Api specification

+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
api endpointmethoddescription
Get the list of all the public doormen.
Create a new doorman.
Get the doorman
Update the doorman with new probabilities
Delete the doorman
+
diff --git a/static/partials/doorman.html b/static/partials/doorman.html new file mode 100644 index 0000000..ad4761a --- /dev/null +++ b/static/partials/doorman.html @@ -0,0 +1,29 @@ +
+
+

{{ doorman.name }}

+
+
+ +
+
+ doorman id: {{ doorman.id }} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
diff --git a/static/partials/doormanError.html b/static/partials/doormanError.html new file mode 100644 index 0000000..57692b2 --- /dev/null +++ b/static/partials/doormanError.html @@ -0,0 +1,6 @@ + +
+
+ Error {{ error }} getting doorman {{ id }} +
+
diff --git a/static/partials/doormen.html b/static/partials/doormen.html new file mode 100644 index 0000000..9b2e00c --- /dev/null +++ b/static/partials/doormen.html @@ -0,0 +1,13 @@ +
+
+

doormen

+
+
+ +
+ +
diff --git a/static/partials/home.html b/static/partials/home.html new file mode 100644 index 0000000..3ac67ad --- /dev/null +++ b/static/partials/home.html @@ -0,0 +1,7 @@ +

Welcome to the world best a/b testing library

+ +
+
+ {{ message }} +
+
diff --git a/static/partials/newdoorman.html b/static/partials/newdoorman.html new file mode 100644 index 0000000..7023efa --- /dev/null +++ b/static/partials/newdoorman.html @@ -0,0 +1,54 @@ + +

New Doorman

+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ - +
+
+ +
+
+ +
+
+ +
+ +
+
+ + +
+
+ +
+
+ {{ error }} +
+
+ +
+
+ +
+ +
+ propability remaning: {{ getProbabilityRemaining() }} +
+
+ +
diff --git a/static/scss/_doorman.scss b/static/scss/_doorman.scss new file mode 100644 index 0000000..4df496f --- /dev/null +++ b/static/scss/_doorman.scss @@ -0,0 +1,21 @@ +@import "settings"; +@import url(http://fonts.googleapis.com/css?family=Orbitron:400,900,500,700); + +h1, h2{ + font-family: 'Orbitron', sans-serif; +} + + +.wab-sidebar{ + background: $ghost; + + h1{ + text-align: center; + color: $primary-color; + } +} +.wab-apispect{ + h2{ + text-align: center; + } +} diff --git a/static/scss/_settings.scss b/static/scss/_settings.scss new file mode 100644 index 0000000..65266ba --- /dev/null +++ b/static/scss/_settings.scss @@ -0,0 +1,1453 @@ +// Foundation by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +// + +// Table of Contents +// Foundation Settings +// +// a. Base +// b. Grid +// c. Global +// d. Media Query Ranges +// e. Typography +// 01. Accordion +// 02. Alert Boxes +// 03. Block Grid +// 04. Breadcrumbs +// 05. Buttons +// 06. Button Groups +// 07. Clearing +// 08. Dropdown +// 09. Dropdown Buttons +// 10. Flex Video +// 11. Forms +// 12. Icon Bar +// 13. Inline Lists +// 14. Joyride +// 15. Keystrokes +// 16. Labels +// 17. Magellan +// 18. Off-canvas +// 19. Orbit +// 20. Pagination +// 21. Panels +// 22. Pricing Tables +// 23. Progress Bar +// 24. Range Slider +// 25. Reveal +// 26. Side Nav +// 27. Split Buttons +// 28. Sub Nav +// 29. Switch +// 30. Tables +// 31. Tabs +// 32. Thumbnails +// 33. Tooltips +// 34. Top Bar +// 36. Visibility Classes + +// a. Base +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// This is the default html and body font-size for the base rem value. +// $rem-base: 16px; + +// Allows the use of rem-calc() or lower-bound() in your settings +@import "foundation/functions"; + +// The default font-size is set to 100% of the browser style sheet (usually 16px) +// for compatibility with browser-based text zoom or user-set defaults. + +// Since the typical default browser font-size is 16px, that makes the calculation for grid size. +// If you want your base font-size to be different and not have it affect the grid breakpoints, +// set $rem-base to $base-font-size and make sure $base-font-size is a px value. +// $base-font-size: 100%; + +// The $base-font-size is 100% while $base-line-height is 150% +// $base-line-height: 150%; + +// We use this to control whether or not CSS classes come through in the gem files. +$include-html-classes: true; +// $include-print-styles: true; +$include-html-global-classes: $include-html-classes; + +// b. Grid +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-grid-classes: $include-html-classes; +// $include-xl-html-grid-classes: false; + +// $row-width: rem-calc(1000); +// $total-columns: 12; +// $column-gutter: rem-calc(30); + +// c. Global +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// We use these to define default font stacks +// $font-family-sans-serif: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; +// $font-family-serif: Georgia, Cambria, "Times New Roman", Times, serif; +// $font-family-monospace: Consolas, "Liberation Mono", Courier, monospace; + +// We use these to define default font weights +// $font-weight-normal: normal !default; +// $font-weight-bold: bold !default; + +// $white : #FFFFFF; +// $ghost : #FAFAFA; +// $snow : #F9F9F9; +// $vapor : #F6F6F6; +// $white-smoke : #F5F5F5; +// $silver : #EFEFEF; +// $smoke : #EEEEEE; +// $gainsboro : #DDDDDD; +// $iron : #CCCCCC; +// $base : #AAAAAA; +// $aluminum : #999999; +// $jumbo : #888888; +// $monsoon : #777777; +// $steel : #666666; +// $charcoal : #555555; +// $tuatara : #444444; +// $oil : #333333; +// $jet : #222222; +// $black : #000000; + +// We use these as default colors throughout +// $primary-color: #008CBA; +// $secondary-color: #e7e7e7; +// $alert-color: #f04124; +// $success-color: #43AC6A; +// $warning-color: #f08a24; +// $info-color: #a0d3e8; + +$primary-color: #153450; +$secondary-color: #294052; +$alert-color: #447294; +$success-color: #73AFB6; +$warning-color: #F4D6BC; +$info-color: #F8E4CC; + + +// We use these to control various global styles +// $body-bg: $white; +// $body-font-color: $jet; +// $body-font-family: $font-family-sans-serif; +// $body-font-weight: $font-weight-normal; +// $body-font-style: normal; + +// We use this to control font-smoothing +// $font-smoothing: antialiased; + +// We use these to control text direction settings +// $text-direction: ltr; +// $opposite-direction: right; +// $default-float: left; +// $last-child-float: $opposite-direction; + +// We use these to make sure border radius matches unless we want it different. +// $global-radius: 3px; +// $global-rounded: 1000px; + +// We use these to control inset shadow shiny edges and depressions. +// $shiny-edge-size: 0 1px 0; +// $shiny-edge-color: rgba($white, .5); +// $shiny-edge-active-color: rgba($black, .2); + +// d. Media Query Ranges +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $small-range: (0em, 40em); +// $medium-range: (40.063em, 64em); +// $large-range: (64.063em, 90em); +// $xlarge-range: (90.063em, 120em); +// $xxlarge-range: (120.063em, 99999999em); + +// $screen: "only screen"; + +// $landscape: "#{$screen} and (orientation: landscape)"; +// $portrait: "#{$screen} and (orientation: portrait)"; + +// $small-up: $screen; +// $small-only: "#{$screen} and (max-width: #{upper-bound($small-range)})"; + +// $medium-up: "#{$screen} and (min-width:#{lower-bound($medium-range)})"; +// $medium-only: "#{$screen} and (min-width:#{lower-bound($medium-range)}) and (max-width:#{upper-bound($medium-range)})"; + +// $large-up: "#{$screen} and (min-width:#{lower-bound($large-range)})"; +// $large-only: "#{$screen} and (min-width:#{lower-bound($large-range)}) and (max-width:#{upper-bound($large-range)})"; + +// $xlarge-up: "#{$screen} and (min-width:#{lower-bound($xlarge-range)})"; +// $xlarge-only: "#{$screen} and (min-width:#{lower-bound($xlarge-range)}) and (max-width:#{upper-bound($xlarge-range)})"; + +// $xxlarge-up: "#{$screen} and (min-width:#{lower-bound($xxlarge-range)})"; +// $xxlarge-only: "#{$screen} and (min-width:#{lower-bound($xxlarge-range)}) and (max-width:#{upper-bound($xxlarge-range)})"; + +// Legacy +// $small: $medium-up; +// $medium: $medium-up; +// $large: $large-up; + +// We use this as cursors values for enabling the option of having custom cursors in the whole site's stylesheet +// $cursor-crosshair-value: crosshair; +// $cursor-default-value: default; +// $cursor-pointer-value: pointer; +// $cursor-help-value: help; +// $cursor-text-value: text; + +// e. Typography +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-type-classes: $include-html-classes; + +// We use these to control header font styles +// $header-font-family: $body-font-family; +// $header-font-weight: $font-weight-normal; +// $header-font-style: normal; +// $header-font-color: $jet; +// $header-line-height: 1.4; +// $header-top-margin: .2rem; +// $header-bottom-margin: .5rem; +// $header-text-rendering: optimizeLegibility; + +// We use these to control header font sizes +// $h1-font-size: rem-calc(44); +// $h2-font-size: rem-calc(37); +// $h3-font-size: rem-calc(27); +// $h4-font-size: rem-calc(23); +// $h5-font-size: rem-calc(18); +// $h6-font-size: 1rem; + +// We use these to control header size reduction on small screens +// $h1-font-reduction: rem-calc(10) !default; +// $h2-font-reduction: rem-calc(10) !default; +// $h3-font-reduction: rem-calc(5) !default; +// $h4-font-reduction: rem-calc(5) !default; +// $h5-font-reduction: 0 !default; +// $h6-font-reduction: 0 !default; + +// These control how subheaders are styled. +// $subheader-line-height: 1.4; +// $subheader-font-color: scale-color($header-font-color, $lightness: 35%); +// $subheader-font-weight: $font-weight-normal; +// $subheader-top-margin: .2rem; +// $subheader-bottom-margin: .5rem; + +// A general styling +// $small-font-size: 60%; +// $small-font-color: scale-color($header-font-color, $lightness: 35%); + +// We use these to style paragraphs +// $paragraph-font-family: inherit; +// $paragraph-font-weight: $font-weight-normal; +// $paragraph-font-size: 1rem; +// $paragraph-line-height: 1.6; +// $paragraph-margin-bottom: rem-calc(20); +// $paragraph-aside-font-size: rem-calc(14); +// $paragraph-aside-line-height: 1.35; +// $paragraph-aside-font-style: italic; +// $paragraph-text-rendering: optimizeLegibility; + +// We use these to style tags +// $code-color: $oil; +// $code-font-family: $font-family-monospace; +// $code-font-weight: $font-weight-normal; +// $code-background-color: scale-color($secondary-color, $lightness: 70%); +// $code-border-size: 1px; +// $code-border-style: solid; +// $code-border-color: scale-color($code-background-color, $lightness: -10%); +// $code-padding: rem-calc(2) rem-calc(5) rem-calc(1); + +// We use these to style anchors +// $anchor-text-decoration: none; +// $anchor-text-decoration-hover: none; +// $anchor-font-color: $primary-color; +// $anchor-font-color-hover: scale-color($primary-color, $lightness: -14%); + +// We use these to style the
element +// $hr-border-width: 1px; +// $hr-border-style: solid; +// $hr-border-color: $gainsboro; +// $hr-margin: rem-calc(20); + +// We use these to style lists +// $list-font-family: $paragraph-font-family; +// $list-font-size: $paragraph-font-size; +// $list-line-height: $paragraph-line-height; +// $list-margin-bottom: $paragraph-margin-bottom; +// $list-style-position: outside; +// $list-side-margin: 1.1rem; +// $list-ordered-side-margin: 1.4rem; +// $list-side-margin-no-bullet: 0; +// $list-nested-margin: rem-calc(20); +// $definition-list-header-weight: $font-weight-bold; +// $definition-list-header-margin-bottom: .3rem; +// $definition-list-margin-bottom: rem-calc(12); + +// We use these to style blockquotes +// $blockquote-font-color: scale-color($header-font-color, $lightness: 35%); +// $blockquote-padding: rem-calc(9 20 0 19); +// $blockquote-border: 1px solid $gainsboro; +// $blockquote-cite-font-size: rem-calc(13); +// $blockquote-cite-font-color: scale-color($header-font-color, $lightness: 23%); +// $blockquote-cite-link-color: $blockquote-cite-font-color; + +// Acronym styles +// $acronym-underline: 1px dotted $gainsboro; + +// We use these to control padding and margin +// $microformat-padding: rem-calc(10 12); +// $microformat-margin: rem-calc(0 0 20 0); + +// We use these to control the border styles +// $microformat-border-width: 1px; +// $microformat-border-style: solid; +// $microformat-border-color: $gainsboro; + +// We use these to control full name font styles +// $microformat-fullname-font-weight: $font-weight-bold; +// $microformat-fullname-font-size: rem-calc(15); + +// We use this to control the summary font styles +// $microformat-summary-font-weight: $font-weight-bold; + +// We use this to control abbr padding +// $microformat-abbr-padding: rem-calc(0 1); + +// We use this to control abbr font styles +// $microformat-abbr-font-weight: $font-weight-bold; +// $microformat-abbr-font-decoration: none; + +// 01. Accordion +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-accordion-classes: $include-html-classes; + +// $accordion-navigation-padding: rem-calc(16); +// $accordion-navigation-bg-color: $silver; +// $accordion-navigation-hover-bg-color: scale-color($accordion-navigation-bg-color, $lightness: -5%); +// $accordion-navigation-active-bg-color: scale-color($accordion-navigation-bg-color, $lightness: -3%); +// $accordion-navigation-font-color: $jet; +// $accordion-navigation-font-size: rem-calc(16); +// $accordion-navigation-font-family: $body-font-family; + +// $accordion-content-padding: ($column-gutter/2); +// $accordion-content-active-bg-color: $white; + +// 02. Alert Boxes +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-alert-classes: $include-html-classes; + +// We use this to control alert padding. +// $alert-padding-top: rem-calc(14); +// $alert-padding-default-float: $alert-padding-top; +// $alert-padding-opposite-direction: $alert-padding-top + rem-calc(10); +// $alert-padding-bottom: $alert-padding-top; + +// We use these to control text style. +// $alert-font-weight: $font-weight-normal; +// $alert-font-size: rem-calc(13); +// $alert-font-color: $white; +// $alert-font-color-alt: scale-color($secondary-color, $lightness: -66%); + +// We use this for close hover effect. +// $alert-function-factor: -14%; + +// We use these to control border styles. +// $alert-border-style: solid; +// $alert-border-width: 1px; +// $alert-border-color: scale-color($primary-color, $lightness: $alert-function-factor); +// $alert-bottom-margin: rem-calc(20); + +// We use these to style the close buttons +// $alert-close-color: $oil; +// $alert-close-top: 50%; +// $alert-close-position: rem-calc(4); +// $alert-close-font-size: rem-calc(22); +// $alert-close-opacity: 0.3; +// $alert-close-opacity-hover: 0.5; +// $alert-close-padding: 9px 6px 4px; + +// We use this to control border radius +// $alert-radius: $global-radius; + +// We use this to control transition effects +// $alert-transition-speed: 300ms; +// $alert-transition-ease: ease-out; + +// 03. Block Grid +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-block-grid-classes: $include-html-classes; +// $include-xl-html-block-grid-classes: false; + +// We use this to control the maximum number of block grid elements per row +// $block-grid-elements: 12; +// $block-grid-default-spacing: rem-calc(20); +// $align-block-grid-to-grid: false; + +// Enables media queries for block-grid classes. Set to false if writing semantic HTML. +// $block-grid-media-queries: true; + +// 04. Breadcrumbs +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-nav-classes: $include-html-classes; + +// We use this to set the background color for the breadcrumb container. +// $crumb-bg: scale-color($secondary-color, $lightness: 55%); + +// We use these to set the padding around the breadcrumbs. +// $crumb-padding: rem-calc(9 14 9); +// $crumb-side-padding: rem-calc(12); + +// We use these to control border styles. +// $crumb-function-factor: -10%; +// $crumb-border-size: 1px; +// $crumb-border-style: solid; +// $crumb-border-color: scale-color($crumb-bg, $lightness: $crumb-function-factor); +// $crumb-radius: $global-radius; + +// We use these to set various text styles for breadcrumbs. +// $crumb-font-size: rem-calc(11); +// $crumb-font-color: $primary-color; +// $crumb-font-color-current: $oil; +// $crumb-font-color-unavailable: $aluminum; +// $crumb-font-transform: uppercase; +// $crumb-link-decor: underline; + +// We use these to control the slash between breadcrumbs +// $crumb-slash-color: $base; +// $crumb-slash: "/"; + +// 05. Buttons +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-button-classes: $include-html-classes; + +// We use these to build padding for buttons. +// $button-tny: rem-calc(10); +// $button-sml: rem-calc(14); +// $button-med: rem-calc(16); +// $button-lrg: rem-calc(18); + +// We use this to control the display property. +// $button-display: inline-block; +// $button-margin-bottom: rem-calc(20); + +// We use these to control button text styles. +// $button-font-family: $body-font-family; +// $button-font-color: $white; +// $button-font-color-alt: $oil; +// $button-font-tny: rem-calc(11); +// $button-font-sml: rem-calc(13); +// $button-font-med: rem-calc(16); +// $button-font-lrg: rem-calc(20); +// $button-font-weight: $font-weight-normal; +// $button-font-align: center; + +// We use these to control various hover effects. +// $button-function-factor: -20%; + +// We use these to control button border and hover styles. +// $button-border-width: 0px; +// $button-border-style: solid; +// $button-bg-color: $primary-color; +// $button-bg-hover: scale-color($button-bg-color, $lightness: $button-function-factor); +// $button-border-color: $button-bg-hover; +// $secondary-button-bg-hover: scale-color($secondary-color, $lightness: $button-function-factor); +// $secondary-button-border-color: $secondary-button-bg-hover; +// $success-button-bg-hover: scale-color($success-color, $lightness: $button-function-factor); +// $success-button-border-color: $success-button-bg-hover; +// $alert-button-bg-hover: scale-color($alert-color, $lightness: $button-function-factor); +// $alert-button-border-color: $alert-button-bg-hover; + +// We use this to set the default radius used throughout the core. +// $button-radius: $global-radius; +// $button-round: $global-rounded; + +// We use this to set default opacity and cursor for disabled buttons. +// $button-disabled-opacity: 0.7; +// $button-disabled-cursor: $cursor-default-value; + +// 06. Button Groups +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-button-classes: $include-html-classes; + +// Sets the margin for the right side by default, and the left margin if right-to-left direction is used +// $button-bar-margin-opposite: rem-calc(10); +// $button-group-border-width: 1px; + +// 07. Clearing +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-clearing-classes: $include-html-classes; + +// We use these to set the background colors for parts of Clearing. +// $clearing-bg: $oil; +// $clearing-caption-bg: $clearing-bg; +// $clearing-carousel-bg: rgba(51,51,51,0.8); +// $clearing-img-bg: $clearing-bg; + +// We use these to style the close button +// $clearing-close-color: $iron; +// $clearing-close-size: 30px; + +// We use these to style the arrows +// $clearing-arrow-size: 12px; +// $clearing-arrow-color: $clearing-close-color; + +// We use these to style captions +// $clearing-caption-font-color: $iron; +// $clearing-caption-font-size: 0.875em; +// $clearing-caption-padding: 10px 30px 20px; + +// We use these to make the image and carousel height and style +// $clearing-active-img-height: 85%; +// $clearing-carousel-height: 120px; +// $clearing-carousel-thumb-width: 120px; +// $clearing-carousel-thumb-active-border: 1px solid rgb(255,255,255); + +// 08. Dropdown +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-dropdown-classes: $include-html-classes; + +// We use these to controls height and width styles. +// $f-dropdown-max-width: 200px; +// $f-dropdown-height: auto; +// $f-dropdown-max-height: none; + +// Used for bottom position +// $f-dropdown-margin-top: 2px; + +// Used for right position +// $f-dropdown-margin-left: $f-dropdown-margin-top; + +// Used for left position +// $f-dropdown-margin-right: $f-dropdown-margin-top; + +// Used for top position +// $f-dropdown-margin-bottom: $f-dropdown-margin-top; + +// We use this to control the background color +// $f-dropdown-bg: $white; + +// We use this to set the border styles for dropdowns. +// $f-dropdown-border-style: solid; +// $f-dropdown-border-width: 1px; +// $f-dropdown-border-color: scale-color($white, $lightness: -20%); + +// We use these to style the triangle pip. +// $f-dropdown-triangle-size: 6px; +// $f-dropdown-triangle-color: $white; +// $f-dropdown-triangle-side-offset: 10px; + +// We use these to control styles for the list elements. +// $f-dropdown-list-style: none; +// $f-dropdown-font-color: $charcoal; +// $f-dropdown-font-size: rem-calc(14); +// $f-dropdown-list-padding: rem-calc(5, 10); +// $f-dropdown-line-height: rem-calc(18); +// $f-dropdown-list-hover-bg: $smoke; +// $dropdown-mobile-default-float: 0; + +// We use this to control the styles for when the dropdown has custom content. +// $f-dropdown-content-padding: rem-calc(20); + +// Default radius for dropdown. +// $f-dropdown-radius: $global-radius; + + +// 09. Dropdown Buttons +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-button-classes: $include-html-classes; + +// We use these to set the color of the pip in dropdown buttons +// $dropdown-button-pip-color: $white; +// $dropdown-button-pip-color-alt: $oil; + +// $button-pip-tny: rem-calc(6); +// $button-pip-sml: rem-calc(7); +// $button-pip-med: rem-calc(9); +// $button-pip-lrg: rem-calc(11); + +// We use these to style tiny dropdown buttons +// $dropdown-button-padding-tny: $button-pip-tny * 7; +// $dropdown-button-pip-size-tny: $button-pip-tny; +// $dropdown-button-pip-opposite-tny: $button-pip-tny * 3; +// $dropdown-button-pip-top-tny: (-$button-pip-tny / 2) + rem-calc(1); + +// We use these to style small dropdown buttons +// $dropdown-button-padding-sml: $button-pip-sml * 7; +// $dropdown-button-pip-size-sml: $button-pip-sml; +// $dropdown-button-pip-opposite-sml: $button-pip-sml * 3; +// $dropdown-button-pip-top-sml: (-$button-pip-sml / 2) + rem-calc(1); + +// We use these to style medium dropdown buttons +// $dropdown-button-padding-med: $button-pip-med * 6 + rem-calc(3); +// $dropdown-button-pip-size-med: $button-pip-med - rem-calc(3); +// $dropdown-button-pip-opposite-med: $button-pip-med * 2.5; +// $dropdown-button-pip-top-med: (-$button-pip-med / 2) + rem-calc(2); + +// We use these to style large dropdown buttons +// $dropdown-button-padding-lrg: $button-pip-lrg * 5 + rem-calc(3); +// $dropdown-button-pip-size-lrg: $button-pip-lrg - rem-calc(6); +// $dropdown-button-pip-opposite-lrg: $button-pip-lrg * 2.5; +// $dropdown-button-pip-top-lrg: (-$button-pip-lrg / 2) + rem-calc(3); + +// 10. Flex Video +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-media-classes: $include-html-classes; + +// We use these to control video container padding and margins +// $flex-video-padding-top: rem-calc(25); +// $flex-video-padding-bottom: 67.5%; +// $flex-video-margin-bottom: rem-calc(16); + +// We use this to control widescreen bottom padding +// $flex-video-widescreen-padding-bottom: 56.34%; + +// 11. Forms +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-form-classes: $include-html-classes; + +// We use this to set the base for lots of form spacing and positioning styles +// $form-spacing: rem-calc(16); + +// We use these to style the labels in different ways +// $form-label-pointer: pointer; +// $form-label-font-size: rem-calc(14); +// $form-label-font-weight: $font-weight-normal; +// $form-label-line-height: 1.5; +// $form-label-font-color: scale-color($black, $lightness: 30%); +// $form-label-small-transform: capitalize; +// $form-label-bottom-margin: 0; +// $input-font-family: inherit; +// $input-font-color: rgba(0,0,0,0.75); +// $input-font-size: rem-calc(14); +// $input-bg-color: $white; +// $input-focus-bg-color: scale-color($white, $lightness: -2%); +// $input-border-color: scale-color($white, $lightness: -20%); +// $input-focus-border-color: scale-color($white, $lightness: -40%); +// $input-border-style: solid; +// $input-border-width: 1px; +// $input-border-radius: $global-radius; +// $input-disabled-bg: $gainsboro; +// $input-disabled-cursor: $cursor-default-value; +// $input-box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); + +// We use these to style the fieldset border and spacing. +// $fieldset-border-style: solid; +// $fieldset-border-width: 1px; +// $fieldset-border-color: $gainsboro; +// $fieldset-padding: rem-calc(20); +// $fieldset-margin: rem-calc(18 0); + +// We use these to style the legends when you use them +// $legend-bg: $white; +// $legend-font-weight: $font-weight-bold; +// $legend-padding: rem-calc(0 3); + +// We use these to style the prefix and postfix input elements +// $input-prefix-bg: scale-color($white, $lightness: -5%); +// $input-prefix-border-color: scale-color($white, $lightness: -20%); +// $input-prefix-border-size: 1px; +// $input-prefix-border-type: solid; +// $input-prefix-overflow: hidden; +// $input-prefix-font-color: $oil; +// $input-prefix-font-color-alt: $white; + +// We use this setting to turn on/off HTML5 number spinners (the up/down arrows) +// $input-number-spinners: true; + +// We use these to style the error states for inputs and labels +// $input-error-message-padding: rem-calc(6 9 9); +// $input-error-message-top: -1px; +// $input-error-message-font-size: rem-calc(12); +// $input-error-message-font-weight: $font-weight-normal; +// $input-error-message-font-style: italic; +// $input-error-message-font-color: $white; +// $input-error-message-bg-color: $alert-color; +// $input-error-message-font-color-alt: $oil; + +// We use this to style the glowing effect of inputs when focused +// $input-include-glowing-effect: true; +// $glowing-effect-fade-time: 0.45s; +// $glowing-effect-color: $input-focus-border-color; + +// Select variables +// $select-bg-color: $ghost; +// $select-hover-bg-color: scale-color($select-bg-color, $lightness: -3%); + +// 12. Icon Bar +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// We use these to style the icon-bar and items +// $include-html-icon-bar-classes: $include-html-classes; +// $icon-bar-bg: $oil; +// $icon-bar-font-color: $white; +// $icon-bar-font-color-hover: $icon-bar-font-color; +// $icon-bar-font-size: 1rem; +// $icon-bar-hover-color: $primary-color; +// $icon-bar-icon-color: $white; +// $icon-bar-icon-color-hover: $icon-bar-icon-color; +// $icon-bar-icon-size: 1.875rem; +// $icon-bar-image-width: 1.875rem; +// $icon-bar-image-height: 1.875rem; +// $icon-bar-active-color: $primary-color; +// $icon-bar-item-padding: 1.25rem; + +// 13. Inline Lists +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-inline-list-classes: $include-html-classes; + +// We use this to control the margins and padding of the inline list. +// $inline-list-top-margin: 0; +// $inline-list-opposite-margin: 0; +// $inline-list-bottom-margin: rem-calc(17); +// $inline-list-default-float-margin: rem-calc(-22); +// $inline-list-default-float-list-margin: rem-calc(22); + +// $inline-list-padding: 0; + +// We use this to control the overflow of the inline list. +// $inline-list-overflow: hidden; + +// We use this to control the list items +// $inline-list-display: block; + +// We use this to control any elements within list items +// $inline-list-children-display: block; + +// 14. Joyride +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-joyride-classes: $include-html-classes; + +// Controlling default Joyride styles +// $joyride-tip-bg: $oil; +// $joyride-tip-default-width: 300px; +// $joyride-tip-padding: rem-calc(18 20 24); +// $joyride-tip-border: solid 1px $charcoal; +// $joyride-tip-radius: 4px; +// $joyride-tip-position-offset: 22px; + +// Here, we're setting the tip font styles +// $joyride-tip-font-color: $white; +// $joyride-tip-font-size: rem-calc(14); +// $joyride-tip-header-weight: $font-weight-bold; + +// This changes the nub size +// $joyride-tip-nub-size: 10px; + +// This adjusts the styles for the timer when its enabled +// $joyride-tip-timer-width: 50px; +// $joyride-tip-timer-height: 3px; +// $joyride-tip-timer-color: $steel; + +// This changes up the styles for the close button +// $joyride-tip-close-color: $monsoon; +// $joyride-tip-close-size: 24px; +// $joyride-tip-close-weight: $font-weight-normal; + +// When Joyride is filling the screen, we use this style for the bg +// $joyride-screenfill: rgba(0,0,0,0.5); + +// 15. Keystrokes +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-keystroke-classes: $include-html-classes; + +// We use these to control text styles. +// $keystroke-font: "Consolas", "Menlo", "Courier", monospace; +// $keystroke-font-size: inherit; +// $keystroke-font-color: $jet; +// $keystroke-font-color-alt: $white; +// $keystroke-function-factor: -7%; + +// We use this to control keystroke padding. +// $keystroke-padding: rem-calc(2 4 0); + +// We use these to control background and border styles. +// $keystroke-bg: scale-color($white, $lightness: $keystroke-function-factor); +// $keystroke-border-style: solid; +// $keystroke-border-width: 1px; +// $keystroke-border-color: scale-color($keystroke-bg, $lightness: $keystroke-function-factor); +// $keystroke-radius: $global-radius; + +// 16. Labels +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-label-classes: $include-html-classes; + +// We use these to style the labels +// $label-padding: rem-calc(4 8 4); +// $label-radius: $global-radius; + +// We use these to style the label text +// $label-font-sizing: rem-calc(11); +// $label-font-weight: $font-weight-normal; +// $label-font-color: $oil; +// $label-font-color-alt: $white; +// $label-font-family: $body-font-family; + +// 17. Magellan +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-magellan-classes: $include-html-classes; + +// $magellan-bg: $white; +// $magellan-padding: 0 !important; + +// 18. Off-canvas +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-off-canvas-classes: $include-html-classes; + +// $tabbar-bg: $oil; +// $tabbar-height: rem-calc(45); +// $tabbar-icon-width: $tabbar-height; +// $tabbar-line-height: $tabbar-height; +// $tabbar-color: $white; +// $tabbar-middle-padding: 0 rem-calc(10); + +// Off Canvas Divider Styles +// $tabbar-right-section-border: solid 1px scale-color($tabbar-bg, $lightness: 13%); +// $tabbar-left-section-border: solid 1px scale-color($tabbar-bg, $lightness: -50%); + +// Off Canvas Tab Bar Headers +// $tabbar-header-color: $white; +// $tabbar-header-weight: $font-weight-bold; +// $tabbar-header-line-height: $tabbar-height; +// $tabbar-header-margin: 0; + +// Off Canvas Menu Variables +// $off-canvas-width: rem-calc(250); +// $off-canvas-bg: $oil; +// $off-canvas-bg-hover: scale-color($tabbar-bg, $lightness: -30%); + +// Off Canvas Menu List Variables +// $off-canvas-label-padding: 0.3rem rem-calc(15); +// $off-canvas-label-color: $aluminum; +// $off-canvas-label-text-transform: uppercase; +// $off-canvas-label-font-size: rem-calc(12); +// $off-canvas-label-font-weight: $font-weight-bold; +// $off-canvas-label-bg: $tuatara; +// $off-canvas-label-border-top: 1px solid scale-color($tuatara, $lightness: 14%); +// $off-canvas-label-border-bottom: none; +// $off-canvas-label-margin:0; +// $off-canvas-link-padding: rem-calc(10, 15); +// $off-canvas-link-color: rgba($white, 0.7); +// $off-canvas-link-border-bottom: 1px solid scale-color($off-canvas-bg, $lightness: -25%); +// $off-canvas-back-bg: $tuatara; +// $off-canvas-back-border-top: $off-canvas-label-border-top; +// $off-canvas-back-border-bottom: $off-canvas-label-border-bottom; +// $off-canvas-back-hover-bg: scale-color($off-canvas-back-bg, $lightness: -30%); +// $off-canvas-back-hover-border-top: 1px solid scale-color($off-canvas-label-bg, $lightness: 14%); +// $off-canvas-back-hover-border-bottom: none; + +// Off Canvas Menu Icon Variables +// $tabbar-menu-icon-color: $white; +// $tabbar-menu-icon-hover: scale-color($tabbar-menu-icon-color, $lightness: -30%); + +// $tabbar-menu-icon-text-indent: rem-calc(35); +// $tabbar-menu-icon-width: $tabbar-height; +// $tabbar-menu-icon-height: $tabbar-height; +// $tabbar-menu-icon-padding: 0; + +// $tabbar-hamburger-icon-width: rem-calc(16); +// $tabbar-hamburger-icon-left: false; +// $tabbar-hamburger-icon-top: false; +// $tabbar-hamburger-icon-thickness: 1px; +// $tabbar-hamburger-icon-gap: 6px; + +// Off Canvas Back-Link Overlay +// $off-canvas-overlay-transition: background 300ms ease; +// $off-canvas-overlay-cursor: pointer; +// $off-canvas-overlay-box-shadow: -4px 0 4px rgba($black, 0.5), 4px 0 4px rgba($black, 0.5); +// $off-canvas-overlay-background: rgba($white, 0.2); +// $off-canvas-overlay-background-hover: rgba($white, 0.05); + +// Transition Variables +// $menu-slide: "transform 500ms ease"; + +// 19. Orbit +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-orbit-classes: $include-html-classes; + +// We use these to control the caption styles +// $orbit-container-bg: none; +// $orbit-caption-bg: rgba(51,51,51, 0.8); +// $orbit-caption-font-color: $white; +// $orbit-caption-font-size: rem-calc(14); +// $orbit-caption-position: "bottom"; // Supported values: "bottom", "under" +// $orbit-caption-padding: rem-calc(10 14); +// $orbit-caption-height: auto; + +// We use these to control the left/right nav styles +// $orbit-nav-bg: transparent; +// $orbit-nav-bg-hover: rgba(0,0,0,0.3); +// $orbit-nav-arrow-color: $white; +// $orbit-nav-arrow-color-hover: $white; + +// We use these to control the timer styles +// $orbit-timer-bg: rgba(255,255,255,0.3); +// $orbit-timer-show-progress-bar: true; + +// We use these to control the bullet nav styles +// $orbit-bullet-nav-color: $iron; +// $orbit-bullet-nav-color-active: $aluminum; +// $orbit-bullet-radius: rem-calc(9); + +// We use these to controls the style of slide numbers +// $orbit-slide-number-bg: rgba(0,0,0,0); +// $orbit-slide-number-font-color: $white; +// $orbit-slide-number-padding: rem-calc(5); + +// Hide controls on small +// $orbit-nav-hide-for-small: true; +// $orbit-bullet-hide-for-small: true; +// $orbit-timer-hide-for-small: true; + +// Graceful Loading Wrapper and preloader +// $wrapper-class: "slideshow-wrapper"; +// $preloader-class: "preloader"; + +// 20. Pagination +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-pagination-classes: $include-html-classes; + +// We use these to control the pagination container +// $pagination-height: rem-calc(24); +// $pagination-margin: rem-calc(-5); + +// We use these to set the list-item properties +// $pagination-li-float: $default-float; +// $pagination-li-height: rem-calc(24); +// $pagination-li-font-color: $jet; +// $pagination-li-font-size: rem-calc(14); +// $pagination-li-margin: rem-calc(5); + +// We use these for the pagination anchor links +// $pagination-link-pad: rem-calc(1 10 1); +// $pagination-link-font-color: $aluminum; +// $pagination-link-active-bg: scale-color($white, $lightness: -10%); + +// We use these for disabled anchor links +// $pagination-link-unavailable-cursor: default; +// $pagination-link-unavailable-font-color: $aluminum; +// $pagination-link-unavailable-bg-active: transparent; + +// We use these for currently selected anchor links +// $pagination-link-current-background: $primary-color; +// $pagination-link-current-font-color: $white; +// $pagination-link-current-font-weight: $font-weight-bold; +// $pagination-link-current-cursor: default; +// $pagination-link-current-active-bg: $primary-color; + +// 21. Panels +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-panel-classes: $include-html-classes; + +// We use these to control the background and border styles +// $panel-bg: scale-color($white, $lightness: -5%); +// $panel-border-style: solid; +// $panel-border-size: 1px; + +// We use this % to control how much we darken things on hover +// $panel-function-factor: -11%; +// $panel-border-color: scale-color($panel-bg, $lightness: $panel-function-factor); + +// We use these to set default inner padding and bottom margin +// $panel-margin-bottom: rem-calc(20); +// $panel-padding: rem-calc(20); + +// We use these to set default font colors +// $panel-font-color: $oil; +// $panel-font-color-alt: $white; + +// $panel-header-adjust: true; +// $callout-panel-link-color: $primary-color; + +// 22. Pricing Tables +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-pricing-classes: $include-html-classes; + +// We use this to control the border color +// $price-table-border: solid 1px $gainsboro; + +// We use this to control the bottom margin of the pricing table +// $price-table-margin-bottom: rem-calc(20); + +// We use these to control the title styles +// $price-title-bg: $oil; +// $price-title-padding: rem-calc(15 20); +// $price-title-align: center; +// $price-title-color: $smoke; +// $price-title-weight: $font-weight-normal; +// $price-title-size: rem-calc(16); +// $price-title-font-family: $body-font-family; + +// We use these to control the price styles +// $price-money-bg: $vapor; +// $price-money-padding: rem-calc(15 20); +// $price-money-align: center; +// $price-money-color: $oil; +// $price-money-weight: $font-weight-normal; +// $price-money-size: rem-calc(32); +// $price-money-font-family: $body-font-family; + +// We use these to control the description styles +// $price-bg: $white; +// $price-desc-color: $monsoon; +// $price-desc-padding: rem-calc(15); +// $price-desc-align: center; +// $price-desc-font-size: rem-calc(12); +// $price-desc-weight: $font-weight-normal; +// $price-desc-line-height: 1.4; +// $price-desc-bottom-border: dotted 1px $gainsboro; + +// We use these to control the list item styles +// $price-item-color: $oil; +// $price-item-padding: rem-calc(15); +// $price-item-align: center; +// $price-item-font-size: rem-calc(14); +// $price-item-weight: $font-weight-normal; +// $price-item-bottom-border: dotted 1px $gainsboro; + +// We use these to control the CTA area styles +// $price-cta-bg: $white; +// $price-cta-align: center; +// $price-cta-padding: rem-calc(20 20 0); + +// 23. Progress Bar +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-media-classes: $include-html-classes; + +// We use this to set the progress bar height +// $progress-bar-height: rem-calc(25); +// $progress-bar-color: $vapor ; + +// We use these to control the border styles +// $progress-bar-border-color: scale-color($white, $lightness: 20%); +// $progress-bar-border-size: 1px; +// $progress-bar-border-style: solid; +// $progress-bar-border-radius: $global-radius; + +// We use these to control the margin & padding +// $progress-bar-pad: rem-calc(2); +// $progress-bar-margin-bottom: rem-calc(10); + +// We use these to set the meter colors +// $progress-meter-color: $primary-color; +// $progress-meter-secondary-color: $secondary-color; +// $progress-meter-success-color: $success-color; +// $progress-meter-alert-color: $alert-color; + +// 24. Range Slider +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-range-slider-classes: $include-html-classes; + +// These variables define the slider bar styles +// $range-slider-bar-width: 100%; +// $range-slider-bar-height: rem-calc(16); + +// $range-slider-bar-border-width: 1px; +// $range-slider-bar-border-style: solid; +// $range-slider-bar-border-color: $gainsboro; +// $range-slider-radius: $global-radius; +// $range-slider-round: $global-rounded; +// $range-slider-bar-bg-color: $ghost; + +// Vertical bar styles +// $range-slider-vertical-bar-width: rem-calc(16); +// $range-slider-vertical-bar-height: rem-calc(200); + +// These variables define the slider handle styles +// $range-slider-handle-width: rem-calc(32); +// $range-slider-handle-height: rem-calc(22); +// $range-slider-handle-position-top: rem-calc(-5); +// $range-slider-handle-bg-color: $primary-color; +// $range-slider-handle-border-width: 1px; +// $range-slider-handle-border-style: solid; +// $range-slider-handle-border-color: none; +// $range-slider-handle-radius: $global-radius; +// $range-slider-handle-round: $global-rounded; +// $range-slider-handle-bg-hover-color: scale-color($primary-color, $lightness: -12%); +// $range-slider-handle-cursor: pointer; + +// 25. Reveal +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-reveal-classes: $include-html-classes; + +// We use these to control the style of the reveal overlay. +// $reveal-overlay-bg: rgba($black, .45); +// $reveal-overlay-bg-old: $black; + +// We use these to control the style of the modal itself. +// $reveal-modal-bg: $white; +// $reveal-position-top: rem-calc(100); +// $reveal-default-width: 80%; +// $reveal-max-width: $row-width; +// $reveal-modal-padding: rem-calc(20); +// $reveal-box-shadow: 0 0 10px rgba($black,.4); + +// We use these to style the reveal close button +// $reveal-close-font-size: rem-calc(40); +// $reveal-close-top: rem-calc(8); +// $reveal-close-side: rem-calc(11); +// $reveal-close-color: $base; +// $reveal-close-weight: $font-weight-bold; + +// We use this to set the default radius used throughout the core. +// $reveal-radius: $global-radius; +// $reveal-round: $global-rounded; + +// We use these to control the modal border +// $reveal-border-style: solid; +// $reveal-border-width: 1px; +// $reveal-border-color: $steel; + +// $reveal-modal-class: "reveal-modal"; +// $close-reveal-modal-class: "close-reveal-modal"; + +// 26. Side Nav +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-nav-classes: $include-html-classes; + +// We use this to control padding. +// $side-nav-padding: rem-calc(14 0); + +// We use these to control list styles. +// $side-nav-list-type: none; +// $side-nav-list-position: inside; +// $side-nav-list-margin: rem-calc(0 0 7 0); + +// We use these to control link styles. +// $side-nav-link-color: $primary-color; +// $side-nav-link-color-active: scale-color($side-nav-link-color, $lightness: 30%); +// $side-nav-link-color-hover: scale-color($side-nav-link-color, $lightness: 30%); +// $side-nav-link-bg-hover: hsla(0, 0, 0, 0.025); +// $side-nav-link-margin: 0; +// $side-nav-link-padding: rem-calc(7 14); +// $side-nav-font-size: rem-calc(14); +// $side-nav-font-weight: $font-weight-normal; +// $side-nav-font-weight-active: $side-nav-font-weight; +// $side-nav-font-family: $body-font-family; +// $side-nav-font-family-active: $side-nav-font-family; + +// We use these to control heading styles. +// $side-nav-heading-color: $side-nav-link-color; +// $side-nav-heading-font-size: $side-nav-font-size; +// $side-nav-heading-font-weight: bold; +// $side-nav-heading-text-transform: uppercase; + +// We use these to control border styles +// $side-nav-divider-size: 1px; +// $side-nav-divider-style: solid; +// $side-nav-divider-color: scale-color($white, $lightness: 10%); + +// 27. Split Buttons +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-button-classes: $include-html-classes; + +// We use these to control different shared styles for Split Buttons +// $split-button-function-factor: 10%; +// $split-button-pip-color: $white; +// $split-button-pip-color-alt: $oil; +// $split-button-active-bg-tint: rgba(0,0,0,0.1); + +// We use these to control tiny split buttons +// $split-button-padding-tny: $button-pip-tny * 10; +// $split-button-span-width-tny: $button-pip-tny * 6; +// $split-button-pip-size-tny: $button-pip-tny; +// $split-button-pip-top-tny: $button-pip-tny * 2; +// $split-button-pip-default-float-tny: rem-calc(-6); + +// We use these to control small split buttons +// $split-button-padding-sml: $button-pip-sml * 10; +// $split-button-span-width-sml: $button-pip-sml * 6; +// $split-button-pip-size-sml: $button-pip-sml; +// $split-button-pip-top-sml: $button-pip-sml * 1.5; +// $split-button-pip-default-float-sml: rem-calc(-6); + +// We use these to control medium split buttons +// $split-button-padding-med: $button-pip-med * 9; +// $split-button-span-width-med: $button-pip-med * 5.5; +// $split-button-pip-size-med: $button-pip-med - rem-calc(3); +// $split-button-pip-top-med: $button-pip-med * 1.5; +// $split-button-pip-default-float-med: rem-calc(-6); + +// We use these to control large split buttons +// $split-button-padding-lrg: $button-pip-lrg * 8; +// $split-button-span-width-lrg: $button-pip-lrg * 5; +// $split-button-pip-size-lrg: $button-pip-lrg - rem-calc(6); +// $split-button-pip-top-lrg: $button-pip-lrg + rem-calc(5); +// $split-button-pip-default-float-lrg: rem-calc(-6); + +// 28. Sub Nav +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-nav-classes: $include-html-classes; + +// We use these to control margin and padding +// $sub-nav-list-margin: rem-calc(-4 0 18); +// $sub-nav-list-padding-top: rem-calc(4); + +// We use this to control the definition +// $sub-nav-font-family: $body-font-family; +// $sub-nav-font-size: rem-calc(14); +// $sub-nav-font-color: $aluminum; +// $sub-nav-font-weight: $font-weight-normal; +// $sub-nav-text-decoration: none; +// $sub-nav-padding: rem-calc(3 16); +// $sub-nav-border-radius: 3px; +// $sub-nav-font-color-hover: scale-color($sub-nav-font-color, $lightness: -25%); + +// We use these to control the active item styles +// $sub-nav-active-font-weight: $font-weight-normal; +// $sub-nav-active-bg: $primary-color; +// $sub-nav-active-bg-hover: scale-color($sub-nav-active-bg, $lightness: -14%); +// $sub-nav-active-color: $white; +// $sub-nav-active-padding: $sub-nav-padding; +// $sub-nav-active-cursor: default; + +// $sub-nav-item-divider: ""; +// $sub-nav-item-divider-margin: rem-calc(12); + +// 29. Switch +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-form-classes: $include-html-classes; + +// Controlling border styles and background colors for the switch container +// $switch-border-color: scale-color($white, $lightness: -20%); +// $switch-border-style: solid; +// $switch-border-width: 1px; +// $switch-bg: $white; + +// We use these to control the switch heights for our default classes +// $switch-height-tny: rem-calc(22); +// $switch-height-sml: rem-calc(28); +// $switch-height-med: rem-calc(36); +// $switch-height-lrg: rem-calc(44); +// $switch-bottom-margin: rem-calc(20); + +// We use these to control default font sizes for our classes. +// $switch-font-size-tny: 11px; +// $switch-font-size-sml: 12px; +// $switch-font-size-med: 14px; +// $switch-font-size-lrg: 17px; +// $switch-label-side-padding: 6px; + +// We use these to style the switch-paddle +// $switch-paddle-bg: $white; +// $switch-paddle-fade-to-color: scale-color($switch-paddle-bg, $lightness: -10%); +// $switch-paddle-border-color: scale-color($switch-paddle-bg, $lightness: -35%); +// $switch-paddle-border-width: 1px; +// $switch-paddle-border-style: solid; +// $switch-paddle-transition-speed: .1s; +// $switch-paddle-transition-ease: ease-out; +// $switch-positive-color: scale-color($success-color, $lightness: 94%); +// $switch-negative-color: $white-smoke; + +// Outline Style for tabbing through switches +// $switch-label-outline: 1px dotted $jumbo; + +// 30. Tables +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-table-classes: $include-html-classes; + +// These control the background color for the table and even rows +// $table-bg: $white; +// $table-even-row-bg: $snow; + +// These control the table cell border style +// $table-border-style: solid; +// $table-border-size: 1px; +// $table-border-color: $gainsboro; + +// These control the table head styles +// $table-head-bg: $white-smoke ; +// $table-head-font-size: rem-calc(14); +// $table-head-font-color: $jet; +// $table-head-font-weight: $font-weight-bold; +// $table-head-padding: rem-calc(8 10 10); + +// These control the row padding and font styles +// $table-row-padding: rem-calc(9 10); +// $table-row-font-size: rem-calc(14); +// $table-row-font-color: $jet; +// $table-line-height: rem-calc(18); + +// These are for controlling the layout, display and margin of tables +// $table-layout: auto; +// $table-display: table-cell; +// $table-margin-bottom: rem-calc(20); + +// 31. Tabs +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-tabs-classes: $include-html-classes; + +// $tabs-navigation-padding: rem-calc(16); +// $tabs-navigation-bg-color: $silver ; +// $tabs-navigation-active-bg-color: $white; +// $tabs-navigation-hover-bg-color: scale-color($tabs-navigation-bg-color, $lightness: -6%); +// $tabs-navigation-font-color: $jet; +// $tabs-navigation-active-font-color: $tabs-navigation-font-color; +// $tabs-navigation-font-size: rem-calc(16); +// $tabs-navigation-font-family: $body-font-family; + +// $tabs-content-margin-bottom: rem-calc(24); +// $tabs-content-padding: ($column-gutter/2); + +// $tabs-vertical-navigation-margin-bottom: 1.25rem; + +// 32. Thumbnails +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-media-classes: $include-html-classes; + +// We use these to control border styles +// $thumb-border-style: solid; +// $thumb-border-width: 4px; +// $thumb-border-color: $white; +// $thumb-box-shadow: 0 0 0 1px rgba($black,.2); +// $thumb-box-shadow-hover: 0 0 6px 1px rgba($primary-color,0.5); + +// Radius and transition speed for thumbs +// $thumb-radius: $global-radius; +// $thumb-transition-speed: 200ms; + +// 33. Tooltips +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-tooltip-classes: $include-html-classes; + +// $has-tip-border-bottom: dotted 1px $iron; +// $has-tip-font-weight: $font-weight-bold; +// $has-tip-font-color: $oil; +// $has-tip-border-bottom-hover: dotted 1px scale-color($primary-color, $lightness: -55%); +// $has-tip-font-color-hover: $primary-color; +// $has-tip-cursor-type: help; + +// $tooltip-padding: rem-calc(12); +// $tooltip-bg: $oil; +// $tooltip-font-size: rem-calc(14); +// $tooltip-font-weight: $font-weight-normal; +// $tooltip-font-color: $white; +// $tooltip-line-height: 1.3; +// $tooltip-close-font-size: rem-calc(10); +// $tooltip-close-font-weight: $font-weight-normal; +// $tooltip-close-font-color: $monsoon; +// $tooltip-font-size-sml: rem-calc(14); +// $tooltip-radius: $global-radius; +// $tooltip-rounded: $global-rounded; +// $tooltip-pip-size: 5px; +// $tooltip-max-width: 300px; + +// 34. Top Bar +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-top-bar-classes: $include-html-classes; + +// Background color for the top bar +// $topbar-bg-color: $oil; +// $topbar-bg: $topbar-bg-color; + +// Height and margin +// $topbar-height: rem-calc(45); +// $topbar-margin-bottom: 0; + +// Controlling the styles for the title in the top bar +// $topbar-title-weight: $font-weight-normal; +// $topbar-title-font-size: rem-calc(17); + +// Style the top bar dropdown elements +// $topbar-dropdown-bg: $oil; +// $topbar-dropdown-link-color: $white; +// $topbar-dropdown-link-bg: $oil; +// $topbar-dropdown-link-bg-hover: $oil; +// $topbar-dropdown-link-weight: $font-weight-normal; +// $topbar-dropdown-toggle-size: 5px; +// $topbar-dropdown-toggle-color: $white; +// $topbar-dropdown-toggle-alpha: 0.4; + +// Set the link colors and styles for top-level nav +// $topbar-link-color: $white; +// $topbar-link-color-hover: $white; +// $topbar-link-color-active: $white; +// $topbar-link-color-active-hover: $white; +// $topbar-link-weight: $font-weight-normal; +// $topbar-link-font-size: rem-calc(13); +// $topbar-link-hover-lightness: -10%; // Darken by 10% +// $topbar-link-bg: $topbar-bg; +// $topbar-link-bg-color-hover: $charcoal; +// $topbar-link-bg-hover: $oil; +// $topbar-link-bg-active: $primary-color; +// $topbar-link-bg-active-hover: scale-color($primary-color, $lightness: -14%); +// $topbar-link-font-family: $body-font-family; +// $topbar-link-text-transform: none; +// $topbar-link-padding: ($topbar-height / 3); +// $topbar-back-link-size: $h5-font-size; +// $topbar-link-dropdown-padding: 20px; + +// $topbar-button-font-size: 0.75rem; +// $topbar-button-top: 7px; + +// $topbar-dropdown-label-color: $monsoon; +// $topbar-dropdown-label-text-transform: uppercase; +// $topbar-dropdown-label-font-weight: $font-weight-bold; +// $topbar-dropdown-label-font-size: rem-calc(10); +// $topbar-dropdown-label-bg: $oil; + +// Top menu icon styles +// $topbar-menu-link-transform: uppercase; +// $topbar-menu-link-font-size: rem-calc(13); +// $topbar-menu-link-weight: $font-weight-bold; +// $topbar-menu-link-color: $white; +// $topbar-menu-icon-color: $white; +// $topbar-menu-link-color-toggled: $jumbo; +// $topbar-menu-icon-color-toggled: $jumbo; + +// Transitions and breakpoint styles +// $topbar-transition-speed: 300ms; +// Using rem-calc for the below breakpoint causes issues with top bar +// $topbar-breakpoint: #{lower-bound($medium-range)}; // Change to 9999px for always mobile layout +// $topbar-media-query: $medium-up; + +// Divider Styles +// $topbar-divider-border-bottom: solid 1px scale-color($topbar-bg-color, $lightness: 13%); +// $topbar-divider-border-top: solid 1px scale-color($topbar-bg-color, $lightness: -50%); + +// Sticky Class +// $topbar-sticky-class: ".sticky"; +// $topbar-arrows: true; //Set false to remove the triangle icon from the menu item + +// 36. Visibility Classes +// - - - - - - - - - - - - - - - - - - - - - - - - - + +// $include-html-visibility-classes: $include-html-classes; +// $include-table-visibility-classes: true; +// $include-legacy-visibility-classes: true; +// $include-accessibility-classes: true; diff --git a/static/scss/style.scss b/static/scss/style.scss new file mode 100644 index 0000000..5a8a37e --- /dev/null +++ b/static/scss/style.scss @@ -0,0 +1,41 @@ +@import "settings"; +@import "foundation"; +@import "doorman"; + +// Or selectively include components +// @import +// "foundation/components/accordion", +// "foundation/components/alert-boxes", +// "foundation/components/block-grid", +// "foundation/components/breadcrumbs", +// "foundation/components/button-groups", +// "foundation/components/buttons", +// "foundation/components/clearing", +// "foundation/components/dropdown", +// "foundation/components/dropdown-buttons", +// "foundation/components/flex-video", +// "foundation/components/forms", +// "foundation/components/grid", +// "foundation/components/inline-lists", +// "foundation/components/joyride", +// "foundation/components/keystrokes", +// "foundation/components/labels", +// "foundation/components/magellan", +// "foundation/components/orbit", +// "foundation/components/pagination", +// "foundation/components/panels", +// "foundation/components/pricing-tables", +// "foundation/components/progress-bars", +// "foundation/components/reveal", +// "foundation/components/side-nav", +// "foundation/components/split-buttons", +// "foundation/components/sub-nav", +// "foundation/components/switch", +// "foundation/components/tables", +// "foundation/components/tabs", +// "foundation/components/thumbs", +// "foundation/components/tooltips", +// "foundation/components/top-bar", +// "foundation/components/type", +// "foundation/components/offcanvas", +// "foundation/components/visibility"; diff --git a/static_server.go b/static_server.go new file mode 100644 index 0000000..143dcc1 --- /dev/null +++ b/static_server.go @@ -0,0 +1,20 @@ +package main + +import ( + "net/http" + "path" + + "github.com/gorilla/mux" +) + +const STATIC_DIR_PATH = "static/" + +func serveStatic(router *mux.Router) { + handler := func(w http.ResponseWriter, request *http.Request) { + vars := mux.Vars(request) + filepath := "/" + vars["path"] + w.Header().Set("Cache-Control", "public, max-age=43200") + http.ServeFile(w, request, path.Join(STATIC_DIR_PATH, filepath)) + } + router.HandleFunc("/{path:.*}", handler) +}