├── .gitignore ├── .travis.yml ├── DOCS.md ├── Godeps ├── Godeps.json └── Readme ├── LICENSE ├── README.md ├── concerns ├── direction │ ├── direction.go │ └── direction_test.go ├── point │ ├── point.go │ └── point_test.go └── version │ └── version.go ├── endpoints ├── dungeon │ ├── endpoints.go │ ├── http.go │ ├── image.go │ └── routes.go ├── error.go ├── http.go ├── maze │ ├── endpoints.go │ ├── http.go │ ├── image.go │ └── routes.go ├── music │ ├── endpoints.go │ ├── http.go │ ├── routes.go │ └── wave.go └── route.go ├── main.go ├── services └── maze │ ├── algorithms.go │ ├── generator.go │ ├── generator_test.go │ ├── maze.go │ ├── maze_test.go │ └── service.go └── vendor ├── github.com ├── Aorioli │ └── chopher │ │ ├── hasher │ │ └── hasher.go │ │ ├── karplus │ │ └── karplus.go │ │ ├── note │ │ └── note.go │ │ ├── scale │ │ └── scale.go │ │ ├── song │ │ └── song.go │ │ └── wave │ │ └── wave.go ├── go-kit │ └── kit │ │ ├── LICENSE │ │ ├── endpoint │ │ └── endpoint.go │ │ ├── log │ │ ├── README.md │ │ ├── json_logger.go │ │ ├── log.go │ │ ├── logfmt_logger.go │ │ ├── nop_logger.go │ │ ├── stdlib.go │ │ └── value.go │ │ └── transport │ │ └── http │ │ ├── client.go │ │ ├── encode_decode.go │ │ ├── request_response_funcs.go │ │ └── server.go ├── gorilla │ ├── context │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── context.go │ │ └── doc.go │ ├── handlers │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── canonical.go │ │ ├── compress.go │ │ ├── cors.go │ │ ├── doc.go │ │ ├── handlers.go │ │ ├── proxy_headers.go │ │ └── recovery.go │ └── mux │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── doc.go │ │ ├── mux.go │ │ ├── regexp.go │ │ └── route.go ├── meshiest │ └── go-dungeon │ │ ├── LICENSE │ │ └── dungeon │ │ └── dungeon.go └── pkg │ └── errors │ ├── .gitignore │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ └── errors.go ├── golang.org └── x │ └── net │ ├── LICENSE │ ├── PATENTS │ └── context │ ├── context.go │ ├── ctxhttp │ └── ctxhttp.go │ ├── go17.go │ └── pre_go17.go └── gopkg.in ├── logfmt.v0 ├── LICENSE ├── README.md ├── jsonstring.go └── logfmt.go └── stack.v1 ├── LICENSE.md ├── README.md └── stack.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,go 2 | 3 | ### OSX ### 4 | *.DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | 32 | ### Go ### 33 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 34 | *.o 35 | *.a 36 | *.so 37 | 38 | # Folders 39 | _obj 40 | _test 41 | 42 | # Architecture specific extensions/prefixes 43 | *.[568vq] 44 | [568vq].out 45 | 46 | *.cgo1.go 47 | *.cgo2.c 48 | _cgo_defun.c 49 | _cgo_gotypes.go 50 | _cgo_export.* 51 | 52 | _testmain.go 53 | 54 | *.exe 55 | *.test 56 | *.prof 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.7 4 | script: 5 | - go test -race $(go list ./... | grep -v /vendor/) 6 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/Aorioli/procedural", 3 | "GoVersion": "go1.7", 4 | "GodepVersion": "v74", 5 | "Packages": [ 6 | "./..." 7 | ], 8 | "Deps": [ 9 | { 10 | "ImportPath": "github.com/Aorioli/chopher/hasher", 11 | "Rev": "6e32774a94a608189c78f155eb4e243c1bd376c5" 12 | }, 13 | { 14 | "ImportPath": "github.com/Aorioli/chopher/karplus", 15 | "Rev": "6e32774a94a608189c78f155eb4e243c1bd376c5" 16 | }, 17 | { 18 | "ImportPath": "github.com/Aorioli/chopher/note", 19 | "Rev": "6e32774a94a608189c78f155eb4e243c1bd376c5" 20 | }, 21 | { 22 | "ImportPath": "github.com/Aorioli/chopher/scale", 23 | "Rev": "6e32774a94a608189c78f155eb4e243c1bd376c5" 24 | }, 25 | { 26 | "ImportPath": "github.com/Aorioli/chopher/song", 27 | "Rev": "6e32774a94a608189c78f155eb4e243c1bd376c5" 28 | }, 29 | { 30 | "ImportPath": "github.com/Aorioli/chopher/wave", 31 | "Rev": "6e32774a94a608189c78f155eb4e243c1bd376c5" 32 | }, 33 | { 34 | "ImportPath": "github.com/go-kit/kit/endpoint", 35 | "Rev": "db631d00e59701187cccc1790f1b95cdb494fdd0" 36 | }, 37 | { 38 | "ImportPath": "github.com/go-kit/kit/log", 39 | "Rev": "db631d00e59701187cccc1790f1b95cdb494fdd0" 40 | }, 41 | { 42 | "ImportPath": "github.com/go-kit/kit/transport/http", 43 | "Rev": "db631d00e59701187cccc1790f1b95cdb494fdd0" 44 | }, 45 | { 46 | "ImportPath": "github.com/gorilla/context", 47 | "Rev": "1c83b3eabd45b6d76072b66b746c20815fb2872d" 48 | }, 49 | { 50 | "ImportPath": "github.com/gorilla/handlers", 51 | "Comment": "v1.1-10-g801d6e3", 52 | "Rev": "801d6e3b008914ee888c9ab9b1b379b9a56fbf44" 53 | }, 54 | { 55 | "ImportPath": "github.com/gorilla/mux", 56 | "Rev": "ad4d7a5882b961e07e2626045eb995c022ac6664" 57 | }, 58 | { 59 | "ImportPath": "github.com/meshiest/go-dungeon/dungeon", 60 | "Rev": "1d1d1e7596b886ea415bd8e2ac16ea8ccf305cf3" 61 | }, 62 | { 63 | "ImportPath": "github.com/pkg/errors", 64 | "Comment": "v0.4.0", 65 | "Rev": "d814416a46cbb066b728cfff58d30a986bc9ddbe" 66 | }, 67 | { 68 | "ImportPath": "golang.org/x/net/context", 69 | "Rev": "7553b97266dcbbf78298bd1a2b12d9c9aaae5f40" 70 | }, 71 | { 72 | "ImportPath": "golang.org/x/net/context/ctxhttp", 73 | "Rev": "7553b97266dcbbf78298bd1a2b12d9c9aaae5f40" 74 | }, 75 | { 76 | "ImportPath": "gopkg.in/logfmt.v0", 77 | "Rev": "c50dc9aa845e681529d8e49d3dc11ec0195aacac" 78 | }, 79 | { 80 | "ImportPath": "gopkg.in/stack.v1", 81 | "Comment": "v1.4.0", 82 | "Rev": "0585967eab0016c8e4e2d55ac20585b469574cec" 83 | } 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Adriano Orioli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Procedural generation as a service](https://theorioli.github.io/procedural/) [![Build Status](https://travis-ci.org/Aorioli/procedural.svg?branch=master)](https://travis-ci.org/Aorioli/procedural) 2 | 3 | Online documentation available [here](http://docs.procedural.apiary.io) 4 | 5 | `DOCS.md` contains the same documentation in the [API Blueprint](https://apiblueprint.org/) format 6 | -------------------------------------------------------------------------------- /concerns/direction/direction.go: -------------------------------------------------------------------------------- 1 | package direction 2 | 3 | type Direction uint8 4 | 5 | const ( 6 | North Direction = iota + 1 7 | East 8 | South 9 | West 10 | ) 11 | 12 | func Opposite(d Direction) Direction { 13 | switch d { 14 | case North: 15 | return South 16 | case East: 17 | return West 18 | case South: 19 | return North 20 | case West: 21 | return East 22 | default: 23 | return d 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /concerns/direction/direction_test.go: -------------------------------------------------------------------------------- 1 | package direction 2 | 3 | import "testing" 4 | 5 | func TestOpposite(t *testing.T) { 6 | for i, test := range []struct { 7 | in Direction 8 | out Direction 9 | }{ 10 | { 11 | in: North, 12 | out: South, 13 | }, 14 | { 15 | in: South, 16 | out: North, 17 | }, 18 | { 19 | in: East, 20 | out: West, 21 | }, 22 | { 23 | in: West, 24 | out: East, 25 | }, 26 | 27 | { 28 | in: Direction(12), 29 | out: Direction(12), 30 | }, 31 | } { 32 | out := Opposite(test.in) 33 | if out != test.out { 34 | t.Errorf("Test %d: Expected %v, got %v", i, test.out, out) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /concerns/point/point.go: -------------------------------------------------------------------------------- 1 | package point 2 | 3 | import "github.com/Aorioli/procedural/concerns/direction" 4 | 5 | type Point struct { 6 | X int `json:"x"` 7 | Y int `json:"y"` 8 | } 9 | 10 | func (p Point) AddDirection(d direction.Direction) Point { 11 | switch d { 12 | case direction.North: 13 | return p.Add(Point{X: 0, Y: -1}) 14 | case direction.East: 15 | return p.Add(Point{X: 1, Y: 0}) 16 | case direction.South: 17 | return p.Add(Point{X: 0, Y: 1}) 18 | case direction.West: 19 | return p.Add(Point{X: -1, Y: 0}) 20 | default: 21 | return p 22 | } 23 | } 24 | 25 | func (p Point) Add(a Point) Point { 26 | return Point{X: p.X + a.X, Y: p.Y + a.Y} 27 | } 28 | 29 | func (p Point) Inside(min Point, max Point) bool { 30 | if p.X < min.X || p.X > max.X { 31 | return false 32 | } 33 | 34 | if p.Y < min.Y || p.Y > max.Y { 35 | return false 36 | } 37 | 38 | return true 39 | } 40 | 41 | func (p Point) Distance(other Point) int { 42 | if p.X < other.X { 43 | p.X, other.X = other.X, p.X 44 | } 45 | 46 | if p.Y < other.Y { 47 | p.Y, other.Y = other.Y, p.Y 48 | } 49 | 50 | return (p.X - other.X) + (p.Y - other.Y) 51 | } 52 | -------------------------------------------------------------------------------- /concerns/point/point_test.go: -------------------------------------------------------------------------------- 1 | package point 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Aorioli/procedural/concerns/direction" 7 | ) 8 | 9 | func TestAddDirection(t *testing.T) { 10 | p := Point{} 11 | for i, test := range []struct { 12 | in direction.Direction 13 | out Point 14 | }{ 15 | { 16 | in: direction.North, 17 | out: Point{X: 0, Y: -1}, 18 | }, 19 | { 20 | in: direction.South, 21 | out: Point{X: 0, Y: 1}, 22 | }, 23 | { 24 | in: direction.East, 25 | out: Point{X: 1, Y: 0}, 26 | }, 27 | { 28 | in: direction.West, 29 | out: Point{X: -1, Y: 0}, 30 | }, 31 | { 32 | in: direction.Direction(12), 33 | out: Point{}, 34 | }, 35 | } { 36 | out := p.AddDirection(test.in) 37 | if out != test.out { 38 | t.Errorf("Test %d: Expected %+v, got %+v", i, test.out, out) 39 | } 40 | } 41 | } 42 | 43 | func TestAdd(t *testing.T) { 44 | p := Point{} 45 | for i, test := range []struct { 46 | in Point 47 | out Point 48 | }{ 49 | { 50 | in: Point{X: 0, Y: -1}, 51 | out: Point{X: 0, Y: -1}, 52 | }, 53 | { 54 | in: Point{X: 0, Y: 1}, 55 | out: Point{X: 0, Y: 1}, 56 | }, 57 | { 58 | in: Point{X: 1, Y: 0}, 59 | out: Point{X: 1, Y: 0}, 60 | }, 61 | { 62 | in: Point{X: -1, Y: 0}, 63 | out: Point{X: -1, Y: 0}, 64 | }, 65 | } { 66 | out := p.Add(test.in) 67 | if out != test.out { 68 | t.Errorf("Test %d: Expected %+v, got %+v", i, test.out, out) 69 | } 70 | } 71 | } 72 | 73 | func TestInside(t *testing.T) { 74 | min := Point{} 75 | max := Point{X: 5, Y: 5} 76 | for i, test := range []struct { 77 | in Point 78 | out bool 79 | }{ 80 | { 81 | in: Point{}, 82 | out: true, 83 | }, 84 | { 85 | in: Point{X: 5, Y: 5}, 86 | out: true, 87 | }, 88 | { 89 | in: Point{X: -1, Y: 5}, 90 | out: false, 91 | }, 92 | { 93 | in: Point{X: 2, Y: 6}, 94 | out: false, 95 | }, 96 | } { 97 | out := test.in.Inside(min, max) 98 | if out != test.out { 99 | t.Errorf("Test %d: Expected %t, got %t", i, test.out, out) 100 | } 101 | } 102 | } 103 | 104 | func TestDistance(t *testing.T) { 105 | for i, test := range []struct { 106 | a Point 107 | b Point 108 | out int 109 | }{ 110 | { 111 | a: Point{}, 112 | b: Point{X: 2, Y: 2}, 113 | out: 4, 114 | }, 115 | { 116 | a: Point{X: 3, Y: 0}, 117 | b: Point{X: 2, Y: 2}, 118 | out: 3, 119 | }, 120 | { 121 | a: Point{X: 0, Y: 3}, 122 | b: Point{X: 2, Y: 2}, 123 | out: 3, 124 | }, 125 | { 126 | a: Point{X: 2, Y: 2}, 127 | b: Point{X: 2, Y: 2}, 128 | out: 0, 129 | }, 130 | } { 131 | out := test.a.Distance(test.b) 132 | if out != test.out { 133 | t.Errorf("Test %d: Expected %d, got %d", i, test.out, out) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /concerns/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | type Version struct { 4 | Major uint `json:"major"` 5 | Minor uint `json:"minor"` 6 | Patch uint `json:"patch"` 7 | } 8 | -------------------------------------------------------------------------------- /endpoints/dungeon/endpoints.go: -------------------------------------------------------------------------------- 1 | package dungeon 2 | 3 | import ( 4 | "net/http" 5 | 6 | "math/rand" 7 | 8 | "github.com/Aorioli/procedural/endpoints" 9 | "github.com/go-kit/kit/endpoint" 10 | "github.com/meshiest/go-dungeon/dungeon" 11 | "github.com/pkg/errors" 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | // generateRequest struct 16 | type generateRequest struct { 17 | Size int 18 | Rooms int 19 | Seed int64 20 | } 21 | 22 | func makeGenerateEndpoint() endpoint.Endpoint { 23 | return func(ctx context.Context, request interface{}) (interface{}, error) { 24 | if req, ok := request.(error); ok { 25 | return req, nil 26 | } 27 | 28 | req, ok := request.(*generateRequest) 29 | if !ok { 30 | return endpoints.Err( 31 | errors.New("Invalid generate request"), 32 | http.StatusInternalServerError, 33 | ), nil 34 | } 35 | 36 | grid := make([][]int, req.Size) 37 | for i := 0; i < req.Size; i++ { 38 | grid[i] = make([]int, req.Size) 39 | } 40 | 41 | dngn := &dungeon.Dungeon{ 42 | Size: req.Size, 43 | NumRooms: req.Rooms, 44 | Grid: grid, 45 | NumTries: 30, 46 | MinSize: 3, 47 | MaxSize: 12, 48 | Rooms: []dungeon.Rectangle{}, 49 | Regions: []int{}, 50 | Bounds: dungeon.Rectangle{X: 1, Y: 1, Width: req.Size - 2, Height: req.Size - 2}, 51 | Rand: rand.New(rand.NewSource(req.Seed)), 52 | } 53 | dngn.Generate() 54 | 55 | return *dngn, nil 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /endpoints/dungeon/http.go: -------------------------------------------------------------------------------- 1 | package dungeon 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/Aorioli/procedural/concerns/direction" 10 | "github.com/Aorioli/procedural/concerns/point" 11 | "github.com/Aorioli/procedural/endpoints" 12 | "github.com/meshiest/go-dungeon/dungeon" 13 | ) 14 | 15 | func decodeRequest(sizeLimit int) func(r *http.Request) (interface{}, error) { 16 | return func(r *http.Request) (interface{}, error) { 17 | request := &generateRequest{ 18 | Size: 5, 19 | Rooms: 1, 20 | Seed: 0, 21 | } 22 | 23 | q := r.URL.Query() 24 | 25 | s := q.Get("seed") 26 | if s != "" { 27 | d, err := strconv.ParseInt(s, 10, 64) 28 | if err != nil { 29 | return endpoints.Err(errors.New("Invalid seed value"), http.StatusBadRequest), nil 30 | } 31 | request.Seed = d 32 | } 33 | 34 | s = q.Get("size") 35 | if s != "" { 36 | d, err := strconv.Atoi(s) 37 | if err != nil || d < 3 || d > sizeLimit { 38 | return endpoints.Err(errors.New("Invalid size value"), http.StatusBadRequest), nil 39 | } 40 | request.Size = d 41 | } 42 | 43 | s = q.Get("rooms") 44 | if s != "" { 45 | d, err := strconv.Atoi(s) 46 | if err != nil || d < 1 || d > 500 { 47 | return endpoints.Err(errors.New("Invalid room number value"), http.StatusBadRequest), nil 48 | } 49 | request.Rooms = d 50 | } 51 | return request, nil 52 | } 53 | } 54 | 55 | type generateResponse struct { 56 | Width int `json:"width"` 57 | Height int `json:"height"` 58 | Grid []gridPoint `json:"grid"` 59 | } 60 | 61 | type gridPoint struct { 62 | P point.Point `json:"point"` 63 | N []point.Point `json:"next"` 64 | } 65 | 66 | func encodeJSONResponse(w http.ResponseWriter, response interface{}) error { 67 | errored, err := endpoints.CheckError(w, response) 68 | if err != nil { 69 | return err 70 | } else if errored { 71 | return nil 72 | } 73 | 74 | v, ok := response.(dungeon.Dungeon) 75 | if !ok { 76 | w.WriteHeader(http.StatusInternalServerError) 77 | return nil 78 | } 79 | 80 | resp := generateResponse{ 81 | Width: v.Size, 82 | Height: v.Size, 83 | Grid: dungeonToGridPoints(v.Grid), 84 | } 85 | 86 | w.Header().Add(endpoints.ContentType, endpoints.ApplicationJSON) 87 | return json.NewEncoder(w).Encode(resp) 88 | } 89 | 90 | func dungeonToGridPoints(dungeon [][]int) []gridPoint { 91 | size := len(dungeon) 92 | ret := make([]gridPoint, 0, size*size) 93 | var directions = []direction.Direction{ 94 | direction.North, 95 | direction.South, 96 | direction.East, 97 | direction.West, 98 | } 99 | 100 | min := point.Point{ 101 | X: 0, 102 | Y: 0, 103 | } 104 | 105 | max := point.Point{ 106 | X: size - 1, 107 | Y: size - 1, 108 | } 109 | 110 | for y := 0; y < size; y++ { 111 | for x := 0; x < size; x++ { 112 | p := dungeon[y][x] 113 | if p == 0 { 114 | continue 115 | } 116 | 117 | gp := point.Point{ 118 | X: x, 119 | Y: y, 120 | } 121 | 122 | next := make([]point.Point, 0, 4) 123 | for _, d := range directions { 124 | pn := gp.AddDirection(d) 125 | 126 | if pn.Inside(min, max) && dungeon[pn.Y][pn.X] == 1 { 127 | next = append(next, pn) 128 | } 129 | } 130 | 131 | ret = append(ret, gridPoint{ 132 | P: gp, 133 | N: next, 134 | }) 135 | } 136 | } 137 | return ret 138 | } 139 | -------------------------------------------------------------------------------- /endpoints/dungeon/image.go: -------------------------------------------------------------------------------- 1 | package dungeon 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/png" 7 | "net/http" 8 | 9 | "github.com/Aorioli/procedural/concerns/point" 10 | "github.com/Aorioli/procedural/endpoints" 11 | "github.com/meshiest/go-dungeon/dungeon" 12 | ) 13 | 14 | func encodeImageResponse(w http.ResponseWriter, response interface{}) error { 15 | errored, err := endpoints.CheckError(w, response) 16 | if err != nil { 17 | return err 18 | } else if errored { 19 | return nil 20 | } 21 | 22 | v, ok := response.(dungeon.Dungeon) 23 | if !ok { 24 | w.WriteHeader(http.StatusInternalServerError) 25 | return nil 26 | } 27 | 28 | w.Header().Add(endpoints.ContentType, "image/png") 29 | return png.Encode(w, img(v, 20)) 30 | } 31 | 32 | func drawCell(cell int, min, max point.Point, im *image.NRGBA, col color.Color) { 33 | if cell == 0 { 34 | return 35 | } 36 | 37 | for x := min.X; x < max.X; x++ { 38 | for y := min.Y; y < max.Y; y++ { 39 | im.Set(x, y, col) 40 | if x == min.X { 41 | im.Set(x, y, color.Black) 42 | } else if x == (max.X - 1) { 43 | im.Set(x, y, color.Black) 44 | } else if y == (max.Y - 1) { 45 | im.Set(x, y, color.Black) 46 | } else if y == min.Y { 47 | im.Set(x, y, color.Black) 48 | } 49 | } 50 | } 51 | } 52 | 53 | func img(d dungeon.Dungeon, size int) image.Image { 54 | im := image.NewNRGBA(image.Rect(0, 0, d.Size*size, d.Size*size)) 55 | for y := 0; y < d.Size; y++ { 56 | for x := 0; x < d.Size; x++ { 57 | c := color.White 58 | drawCell( 59 | d.Grid[y][x], 60 | point.Point{X: x * size, Y: y * size}, 61 | point.Point{X: (x + 1) * size, Y: (y + 1) * size}, 62 | im, 63 | c, 64 | ) 65 | } 66 | } 67 | return im 68 | } 69 | -------------------------------------------------------------------------------- /endpoints/dungeon/routes.go: -------------------------------------------------------------------------------- 1 | package dungeon 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Aorioli/procedural/concerns/version" 7 | "github.com/Aorioli/procedural/endpoints" 8 | httptransport "github.com/go-kit/kit/transport/http" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | var ( 13 | serviceIntro = "Dungeon-as-a-service" 14 | serviceVersion = version.Version{ 15 | Major: 0, 16 | Minor: 0, 17 | Patch: 0, 18 | } 19 | ) 20 | 21 | // HTTP returns the created routes 22 | func HTTP(ctx context.Context) []endpoints.Route { 23 | return []endpoints.Route{ 24 | { 25 | Path: "/", 26 | Method: http.MethodGet, 27 | Handler: endpoints.Description(serviceIntro, serviceVersion), 28 | }, 29 | { 30 | Path: "/generate", 31 | Method: http.MethodGet, 32 | Handler: httptransport.NewServer( 33 | ctx, 34 | makeGenerateEndpoint(), 35 | decodeRequest(500), 36 | encodeJSONResponse, 37 | ), 38 | }, 39 | { 40 | Path: "/generate/image", 41 | Method: http.MethodGet, 42 | Handler: httptransport.NewServer( 43 | ctx, 44 | makeGenerateEndpoint(), 45 | decodeRequest(100), 46 | encodeImageResponse, 47 | ), 48 | }, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /endpoints/error.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | // Error is a wrapper struct that provides HTTP Status code 10 | // to an error 11 | type Error struct { 12 | Cause error 13 | Status int 14 | } 15 | 16 | func (e Error) Error() string { 17 | return fmt.Sprintf("%s", e.Cause.Error()) 18 | } 19 | 20 | // Err wraps a cause with a status code 21 | func Err(cause error, status int) error { 22 | return Error{ 23 | Cause: cause, 24 | Status: status, 25 | } 26 | } 27 | 28 | // MarshalJSON satisfies json.Marshaler 29 | func (e Error) MarshalJSON() ([]byte, error) { 30 | r := map[string]string{ 31 | "error": e.Cause.Error(), 32 | } 33 | 34 | return json.Marshal(r) 35 | } 36 | 37 | func CheckError(w http.ResponseWriter, response interface{}) (bool, error) { 38 | switch v := response.(type) { 39 | case Error: 40 | w.Header().Add(ContentType, ApplicationJSON) 41 | w.WriteHeader(v.Status) 42 | return true, json.NewEncoder(w).Encode(v) 43 | case error: 44 | w.Header().Add(ContentType, ApplicationJSON) 45 | w.WriteHeader(http.StatusInternalServerError) 46 | return true, json.NewEncoder(w).Encode(v) 47 | } 48 | 49 | return false, nil 50 | } 51 | -------------------------------------------------------------------------------- /endpoints/http.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Aorioli/procedural/concerns/version" 8 | ) 9 | 10 | type descriptionServer struct { 11 | Intro string `json:"intro"` 12 | Version version.Version `json:"version"` 13 | } 14 | 15 | func (m descriptionServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 16 | w.Header().Add(ContentType, "application/json") 17 | json.NewEncoder(w).Encode(m) 18 | } 19 | 20 | func Description(intro string, v version.Version) http.Handler { 21 | return descriptionServer{ 22 | Intro: intro, 23 | Version: v, 24 | } 25 | } 26 | 27 | const ( 28 | ContentType = "Content-Type" 29 | ApplicationJSON = "application/json" 30 | ) 31 | 32 | // EncodeResponse encodes a generic response 33 | func EncodeResponse(w http.ResponseWriter, response interface{}) error { 34 | switch v := response.(type) { 35 | case Error: 36 | w.Header().Add(ContentType, ApplicationJSON) 37 | w.WriteHeader(v.Status) 38 | return json.NewEncoder(w).Encode(v) 39 | case error: 40 | w.Header().Add(ContentType, ApplicationJSON) 41 | w.WriteHeader(http.StatusInternalServerError) 42 | return json.NewEncoder(w).Encode(v) 43 | } 44 | 45 | w.Header().Add(ContentType, ApplicationJSON) 46 | w.WriteHeader(http.StatusOK) 47 | return json.NewEncoder(w).Encode(response) 48 | } 49 | -------------------------------------------------------------------------------- /endpoints/maze/endpoints.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Aorioli/procedural/endpoints" 7 | "github.com/Aorioli/procedural/services/maze" 8 | "github.com/go-kit/kit/endpoint" 9 | "github.com/pkg/errors" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // generateRequest struct 14 | type generateRequest struct { 15 | width int 16 | height int 17 | seed int64 18 | } 19 | 20 | func makeGenerateEndpoint(svc maze.Service, algorithm maze.Chooser) endpoint.Endpoint { 21 | return func(ctx context.Context, request interface{}) (interface{}, error) { 22 | if req, ok := request.(error); ok { 23 | return req, nil 24 | } 25 | 26 | req, ok := request.(*generateRequest) 27 | if !ok { 28 | return endpoints.Err( 29 | errors.New("Invalid generate request"), 30 | http.StatusInternalServerError, 31 | ), nil 32 | } 33 | m := svc.Generate(req.width, req.height, req.seed, algorithm) 34 | return m, nil 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /endpoints/maze/http.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/Aorioli/procedural/concerns/point" 10 | "github.com/Aorioli/procedural/endpoints" 11 | "github.com/Aorioli/procedural/services/maze" 12 | ) 13 | 14 | func decodeRequest(sizeLimit int) func(r *http.Request) (interface{}, error) { 15 | return func(r *http.Request) (interface{}, error) { 16 | request := &generateRequest{ 17 | width: 10, 18 | height: 10, 19 | } 20 | 21 | q := r.URL.Query() 22 | 23 | s := q.Get("seed") 24 | if s != "" { 25 | d, err := strconv.ParseInt(s, 10, 64) 26 | if err != nil { 27 | return endpoints.Err(errors.New("Invalid seed value"), http.StatusBadRequest), nil 28 | } 29 | request.seed = d 30 | } 31 | 32 | s = q.Get("w") 33 | if s != "" { 34 | d, err := strconv.Atoi(s) 35 | if err != nil || d <= 0 || d > sizeLimit { 36 | return endpoints.Err(errors.New("Invalid width value"), http.StatusBadRequest), nil 37 | } 38 | request.width = d 39 | } 40 | 41 | s = q.Get("h") 42 | if s != "" { 43 | d, err := strconv.Atoi(s) 44 | if err != nil || d <= 0 || d > sizeLimit { 45 | return endpoints.Err(errors.New("Invalid height value"), http.StatusBadRequest), nil 46 | } 47 | request.height = d 48 | } 49 | return request, nil 50 | } 51 | } 52 | 53 | type gridPoint struct { 54 | P point.Point `json:"point"` 55 | N []point.Point `json:"next"` 56 | } 57 | 58 | type generateResponse struct { 59 | Width int `json:"width"` 60 | Height int `json:"height"` 61 | Entrance point.Point `json:"entrance"` 62 | Exit point.Point `json:"exit"` 63 | Grid []gridPoint `json:"grid"` 64 | } 65 | 66 | func encodeJSONResponse(w http.ResponseWriter, response interface{}) error { 67 | errored, err := endpoints.CheckError(w, response) 68 | if err != nil { 69 | return err 70 | } else if errored { 71 | return nil 72 | } 73 | 74 | v, ok := response.(maze.Maze) 75 | if !ok { 76 | w.WriteHeader(http.StatusInternalServerError) 77 | return nil 78 | } 79 | 80 | resp := generateResponse{ 81 | Width: v.Width, 82 | Height: v.Height, 83 | Entrance: v.Entrance, 84 | Exit: v.Exit, 85 | } 86 | 87 | g := make([]gridPoint, 0, len(v.Grid)) 88 | for p, c := range v.Grid { 89 | gp := gridPoint{ 90 | P: p, 91 | } 92 | 93 | n := make([]point.Point, len(c.Next)) 94 | for i := 0; i < len(c.Next); i++ { 95 | n[i] = p.AddDirection(c.Next[i]) 96 | } 97 | 98 | gp.N = n 99 | g = append(g, gp) 100 | } 101 | resp.Grid = g 102 | 103 | w.Header().Add(endpoints.ContentType, endpoints.ApplicationJSON) 104 | return json.NewEncoder(w).Encode(resp) 105 | } 106 | -------------------------------------------------------------------------------- /endpoints/maze/image.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/png" 7 | "net/http" 8 | 9 | "github.com/Aorioli/procedural/concerns/direction" 10 | "github.com/Aorioli/procedural/concerns/point" 11 | "github.com/Aorioli/procedural/endpoints" 12 | "github.com/Aorioli/procedural/services/maze" 13 | ) 14 | 15 | func encodeImageResponse(w http.ResponseWriter, response interface{}) error { 16 | errored, err := endpoints.CheckError(w, response) 17 | if err != nil { 18 | return err 19 | } else if errored { 20 | return nil 21 | } 22 | 23 | v, ok := response.(maze.Maze) 24 | if !ok { 25 | w.WriteHeader(http.StatusInternalServerError) 26 | return nil 27 | } 28 | 29 | w.Header().Add(endpoints.ContentType, "image/png") 30 | return png.Encode(w, img(v, 20)) 31 | } 32 | 33 | func drawCell(c maze.Cell, min, max point.Point, im *image.NRGBA, col color.Color) { 34 | for x := min.X; x < max.X; x++ { 35 | for y := min.Y; y < max.Y; y++ { 36 | im.Set(x, y, col) 37 | if x == min.X && !c.Has(direction.West) { 38 | im.Set(x, y, color.Black) 39 | } else if x == (max.X-1) && !c.Has(direction.East) { 40 | im.Set(x, y, color.Black) 41 | } else if y == (max.Y-1) && !c.Has(direction.South) { 42 | im.Set(x, y, color.Black) 43 | } else if y == min.Y && !c.Has(direction.North) { 44 | im.Set(x, y, color.Black) 45 | } 46 | } 47 | } 48 | } 49 | 50 | func img(m maze.Maze, size int) image.Image { 51 | im := image.NewNRGBA(image.Rect(0, 0, m.Width*size, m.Height*size)) 52 | for y := 0; y <= m.Height; y++ { 53 | for x := 0; x <= m.Width; x++ { 54 | p := point.Point{X: x, Y: y} 55 | var c color.Color 56 | if p == m.Entrance { 57 | c = color.NRGBA{ 58 | R: 0x00, 59 | G: 0x0a, 60 | B: 0xff, 61 | A: 0xff, 62 | } 63 | } else if p == m.Exit { 64 | c = color.NRGBA{ 65 | R: 0xff, 66 | G: 0x00, 67 | B: 0x0a, 68 | A: 0xff, 69 | } 70 | } else { 71 | c = color.White 72 | } 73 | if _, ok := m.Grid[p]; ok { 74 | drawCell( 75 | m.Grid[p], 76 | point.Point{X: x * size, Y: y * size}, 77 | point.Point{X: (x + 1) * size, Y: (y + 1) * size}, 78 | im, 79 | c, 80 | ) 81 | } 82 | } 83 | } 84 | return im 85 | } 86 | -------------------------------------------------------------------------------- /endpoints/maze/routes.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Aorioli/procedural/concerns/version" 7 | "github.com/Aorioli/procedural/endpoints" 8 | "github.com/Aorioli/procedural/services/maze" 9 | httptransport "github.com/go-kit/kit/transport/http" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | var ( 14 | serviceIntro = "Maze-as-a-service" 15 | serviceVersion = version.Version{ 16 | Major: 0, 17 | Minor: 0, 18 | Patch: 0, 19 | } 20 | ) 21 | 22 | // HTTP returns the created routes 23 | func HTTP(svc maze.Service, ctx context.Context) []endpoints.Route { 24 | return []endpoints.Route{ 25 | { 26 | Path: "/", 27 | Method: http.MethodGet, 28 | Handler: endpoints.Description(serviceIntro, serviceVersion), 29 | }, 30 | { 31 | Path: "/generate/backtrack", 32 | Method: http.MethodGet, 33 | Handler: httptransport.NewServer( 34 | ctx, 35 | makeGenerateEndpoint(svc, maze.Backtrack()), 36 | decodeRequest(500), 37 | encodeJSONResponse, 38 | ), 39 | }, 40 | { 41 | Path: "/generate/backtrack/image", 42 | Method: http.MethodGet, 43 | Handler: httptransport.NewServer( 44 | ctx, 45 | makeGenerateEndpoint(svc, maze.Backtrack()), 46 | decodeRequest(100), 47 | encodeImageResponse, 48 | ), 49 | }, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /endpoints/music/endpoints.go: -------------------------------------------------------------------------------- 1 | package music 2 | 3 | import ( 4 | "io" 5 | "math/rand" 6 | "net/http" 7 | 8 | "github.com/Aorioli/chopher/hasher" 9 | "github.com/Aorioli/chopher/karplus" 10 | "github.com/Aorioli/chopher/note" 11 | "github.com/Aorioli/chopher/scale" 12 | "github.com/Aorioli/chopher/song" 13 | 14 | "github.com/Aorioli/procedural/endpoints" 15 | "github.com/go-kit/kit/endpoint" 16 | "github.com/pkg/errors" 17 | "golang.org/x/net/context" 18 | ) 19 | 20 | // generateRequest struct 21 | type generateRequest struct { 22 | length int 23 | seed int64 24 | smoke bool 25 | } 26 | 27 | type byteGenerator struct { 28 | length int64 29 | processed int64 30 | rnd *rand.Rand 31 | } 32 | 33 | func (b *byteGenerator) Read(d []byte) (int, error) { 34 | requested := len(d) 35 | var eof error 36 | if int64(requested) > b.length { 37 | requested = int(b.length) 38 | eof = io.EOF 39 | } 40 | 41 | for i := 0; i < requested; i++ { 42 | d[i] = byte(b.rnd.Uint32() % 256) 43 | } 44 | 45 | return requested, eof 46 | } 47 | 48 | func makeGenerateEndpoint() endpoint.Endpoint { 49 | return func(ctx context.Context, request interface{}) (interface{}, error) { 50 | if req, ok := request.(error); ok { 51 | return req, nil 52 | } 53 | 54 | req, ok := request.(*generateRequest) 55 | if !ok { 56 | return endpoints.Err( 57 | errors.New("Invalid generate request"), 58 | http.StatusInternalServerError, 59 | ), nil 60 | } 61 | var sng *song.Song 62 | if req.smoke { 63 | s := song.New(song.Medium * 1.5) 64 | sng = s.Add(note.New(note.D, 2), note.Half+note.Quarter). 65 | AddWith(note.New(note.G, 2), note.Half+note.Quarter). 66 | AddAfter(note.New(note.F, 2), note.Half+note.Quarter). 67 | AddWith(note.New(note.AIS, 2), note.Half+note.Quarter). 68 | AddAfter(note.New(note.G, 2), note.Full). 69 | AddWith(note.New(note.C, 2), note.Full). 70 | AddAfter(note.New(note.D, 2), note.Half+note.Quarter). 71 | AddWith(note.New(note.G, 2), note.Half+note.Quarter). 72 | AddAfter(note.New(note.F, 2), note.Half+note.Quarter). 73 | AddWith(note.New(note.AIS, 2), note.Half+note.Quarter). 74 | AddAfter(note.New(note.GIS, 2), note.Half). 75 | AddWith(note.New(note.CIS, 2), note.Half). 76 | AddAfter(note.New(note.G, 2), note.Full). 77 | AddWith(note.New(note.C, 2), note.Full). 78 | AddAfter(note.New(note.D, 2), note.Half+note.Quarter). 79 | AddWith(note.New(note.G, 2), note.Half+note.Quarter). 80 | AddAfter(note.New(note.F, 2), note.Half+note.Quarter). 81 | AddWith(note.New(note.AIS, 2), note.Half+note.Quarter). 82 | AddAfter(note.New(note.G, 2), note.Full). 83 | AddWith(note.New(note.C, 2), note.Full). 84 | AddAfter(note.New(note.F, 2), note.Half+note.Quarter). 85 | AddWith(note.New(note.AIS, 2), note.Half+note.Quarter). 86 | AddAfter(note.New(note.D, 2), note.Full*2). 87 | AddWith(note.New(note.G, 2), note.Full*2) 88 | 89 | sng.Scale = scale.Minor.New(note.New(note.G, 2), false) 90 | } else { 91 | b := &byteGenerator{ 92 | length: 64 + int64(req.length), 93 | rnd: rand.New(rand.NewSource(req.seed)), 94 | } 95 | h := hasher.New(b) 96 | 97 | sng = h.Hash() 98 | } 99 | 100 | ks := karplus.Song{ 101 | Song: *sng, 102 | SamplingRate: 22000, 103 | } 104 | 105 | return ks, nil 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /endpoints/music/http.go: -------------------------------------------------------------------------------- 1 | package music 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/Aorioli/chopher/karplus" 10 | "github.com/Aorioli/chopher/note" 11 | "github.com/Aorioli/chopher/song" 12 | "github.com/Aorioli/procedural/endpoints" 13 | ) 14 | 15 | func decodeRequest(sizeLimit int) func(r *http.Request) (interface{}, error) { 16 | return func(r *http.Request) (interface{}, error) { 17 | request := &generateRequest{ 18 | length: 10, 19 | } 20 | 21 | q := r.URL.Query() 22 | 23 | s := q.Get("seed") 24 | if s != "" { 25 | d, err := strconv.ParseInt(s, 10, 64) 26 | if err != nil { 27 | return endpoints.Err(errors.New("Invalid seed value"), http.StatusBadRequest), nil 28 | } 29 | request.seed = d 30 | } 31 | 32 | s = q.Get("size") 33 | if s != "" { 34 | d, err := strconv.Atoi(s) 35 | if err != nil || d <= 0 || d > sizeLimit { 36 | return endpoints.Err(errors.New("Invalid size value"), http.StatusBadRequest), nil 37 | } 38 | request.length = d 39 | } 40 | 41 | s = q.Get("smoke_on_the_water") 42 | if s != "" { 43 | request.smoke = (s == "true") 44 | } 45 | 46 | return request, nil 47 | } 48 | } 49 | 50 | type jsonResponse struct { 51 | Scale []int `json:"scale"` 52 | Key jsonNote `json:"key"` 53 | Tempo song.Tempo `json:"tempo"` 54 | Song []jsonSongNote `json:"song"` 55 | } 56 | 57 | type jsonNote struct { 58 | N string `json:"note"` 59 | O int `json:"octave"` 60 | F float64 `json:"frequency"` 61 | } 62 | 63 | type jsonSongNote struct { 64 | jsonNote 65 | Duration note.Duration `json:"duration"` 66 | StartAt float64 `json:"start_at"` 67 | } 68 | 69 | func encodeJSONResponse(w http.ResponseWriter, response interface{}) error { 70 | errored, err := endpoints.CheckError(w, response) 71 | if err != nil { 72 | return err 73 | } else if errored { 74 | return nil 75 | } 76 | 77 | v, ok := response.(karplus.Song) 78 | if !ok { 79 | w.WriteHeader(http.StatusInternalServerError) 80 | return nil 81 | } 82 | 83 | resp := jsonResponse{ 84 | Scale: v.Song.Scale.Pattern.Scale, 85 | Key: toNote(v.Song.Scale.Key), 86 | Tempo: v.Song.Tempo, 87 | Song: toSongNote(v.Song.Notes), 88 | } 89 | 90 | w.Header().Add(endpoints.ContentType, endpoints.ApplicationJSON) 91 | return json.NewEncoder(w).Encode(resp) 92 | } 93 | 94 | func toNote(n note.Note) jsonNote { 95 | return jsonNote{ 96 | F: n.Frequency(), 97 | O: n.Octave, 98 | N: note.Notes[n.Note], 99 | } 100 | } 101 | 102 | func toSongNote(s []song.SongNote) []jsonSongNote { 103 | ret := make([]jsonSongNote, len(s)) 104 | for i := 0; i < len(s); i++ { 105 | ret[i] = jsonSongNote{ 106 | jsonNote: toNote(s[i].Note), 107 | Duration: s[i].Duration, 108 | StartAt: s[i].Start, 109 | } 110 | } 111 | return ret 112 | } 113 | -------------------------------------------------------------------------------- /endpoints/music/routes.go: -------------------------------------------------------------------------------- 1 | package music 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Aorioli/procedural/concerns/version" 7 | "github.com/Aorioli/procedural/endpoints" 8 | httptransport "github.com/go-kit/kit/transport/http" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | var ( 13 | serviceIntro = "Music-as-a-service" 14 | serviceVersion = version.Version{ 15 | Major: 0, 16 | Minor: 0, 17 | Patch: 0, 18 | } 19 | ) 20 | 21 | // HTTP returns the created routes 22 | func HTTP(ctx context.Context) []endpoints.Route { 23 | return []endpoints.Route{ 24 | { 25 | Path: "/", 26 | Method: http.MethodGet, 27 | Handler: endpoints.Description(serviceIntro, serviceVersion), 28 | }, 29 | { 30 | Path: "/generate", 31 | Method: http.MethodGet, 32 | Handler: httptransport.NewServer( 33 | ctx, 34 | makeGenerateEndpoint(), 35 | decodeRequest(500), 36 | encodeJSONResponse, 37 | ), 38 | }, 39 | { 40 | Path: "/generate/wave", 41 | Method: http.MethodGet, 42 | Handler: httptransport.NewServer( 43 | ctx, 44 | makeGenerateEndpoint(), 45 | decodeRequest(500), 46 | encodeWaveResponse, 47 | ), 48 | }, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /endpoints/music/wave.go: -------------------------------------------------------------------------------- 1 | package music 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | "github.com/Aorioli/chopher/karplus" 8 | "github.com/Aorioli/chopher/wave" 9 | "github.com/Aorioli/procedural/endpoints" 10 | ) 11 | 12 | func encodeWaveResponse(w http.ResponseWriter, response interface{}) error { 13 | errored, err := endpoints.CheckError(w, response) 14 | if err != nil { 15 | return err 16 | } else if errored { 17 | return nil 18 | } 19 | 20 | v, ok := response.(karplus.Song) 21 | if !ok { 22 | w.WriteHeader(http.StatusInternalServerError) 23 | return nil 24 | } 25 | 26 | wav := wave.New(wave.Stereo, 22000) 27 | v.Sound(&wav) 28 | w.Header().Add(endpoints.ContentType, "audio/wav") 29 | io.Copy(w, wav.Reader()) 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /endpoints/route.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import "net/http" 4 | 5 | // Route contains endpoint information 6 | type Route struct { 7 | Path string 8 | Method string 9 | Handler http.Handler 10 | } 11 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/gorilla/handlers" 9 | "github.com/gorilla/mux" 10 | "golang.org/x/net/context" 11 | 12 | dungeonEndpoints "github.com/Aorioli/procedural/endpoints/dungeon" 13 | mazeEndpoints "github.com/Aorioli/procedural/endpoints/maze" 14 | musicEndpoints "github.com/Aorioli/procedural/endpoints/music" 15 | mazeService "github.com/Aorioli/procedural/services/maze" 16 | ) 17 | 18 | func main() { 19 | port := os.Getenv("PORT") 20 | if port == "" { 21 | port = "8008" 22 | } 23 | 24 | router := mux.NewRouter().StrictSlash(true) 25 | 26 | mazeSvc := mazeService.New() 27 | root := context.Background() 28 | 29 | mazeRouter := router.PathPrefix("/maze").Subrouter() 30 | for _, route := range mazeEndpoints.HTTP(mazeSvc, root) { 31 | log.Println("/maze" + route.Path) 32 | mazeRouter.Handle(route.Path, route.Handler).Methods(route.Method) 33 | } 34 | 35 | dungeonRouter := router.PathPrefix("/dungeon").Subrouter() 36 | for _, route := range dungeonEndpoints.HTTP(root) { 37 | log.Println("/dungeon" + route.Path) 38 | dungeonRouter.Handle(route.Path, route.Handler).Methods(route.Method) 39 | } 40 | 41 | musicRouter := router.PathPrefix("/music").Subrouter() 42 | for _, route := range musicEndpoints.HTTP(root) { 43 | log.Println("/music" + route.Path) 44 | musicRouter.Handle(route.Path, route.Handler).Methods(route.Method) 45 | } 46 | 47 | log.Fatalln(http.ListenAndServe( 48 | ":"+port, 49 | handlers.LoggingHandler( 50 | os.Stdout, 51 | router, 52 | ), 53 | )) 54 | } 55 | -------------------------------------------------------------------------------- /services/maze/algorithms.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | type backtrack struct{} 4 | 5 | func (b backtrack) Choose(length int) int { 6 | return length - 1 7 | } 8 | 9 | // Backtrack returns a backtrack algorithm implementation 10 | func Backtrack() Chooser { 11 | return backtrack{} 12 | } 13 | -------------------------------------------------------------------------------- /services/maze/generator.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import ( 4 | "github.com/Aorioli/procedural/concerns/direction" 5 | "github.com/Aorioli/procedural/concerns/point" 6 | ) 7 | 8 | // Chooser interface is the interface that the cell picking algorithm should implement 9 | type Chooser interface { 10 | Choose(int) int 11 | } 12 | 13 | // Randomizer abstracts the rand.Rand functions needed, for easier testing 14 | type Randomizer interface { 15 | Int() int 16 | Intn(int) int 17 | Perm(int) []int 18 | } 19 | 20 | func generate(width, height int, r Randomizer, chooser Chooser) Maze { 21 | if width == 1 && height == 1 { 22 | return Maze{ 23 | Width: 1, 24 | Height: 1, 25 | Grid: map[point.Point]Cell{ 26 | point.Point{}: Cell{}, 27 | }, 28 | } 29 | } 30 | 31 | directions := []direction.Direction{ 32 | direction.North, 33 | direction.East, 34 | direction.South, 35 | direction.West, 36 | } 37 | 38 | minBounds := point.Point{X: 0, Y: 0} 39 | maxBounds := point.Point{X: width - 1, Y: height - 1} 40 | 41 | x, y := (r.Int() % width), (r.Int() % height) 42 | entrance := point.Point{ 43 | X: x, 44 | Y: y, 45 | } 46 | 47 | ret := Maze{ 48 | Width: width, 49 | Height: height, 50 | Entrance: entrance, 51 | } 52 | 53 | live := make([]point.Point, 0, (width*height)/2) 54 | live = append(live, entrance) 55 | 56 | visited := make(map[point.Point]struct{}, (width * height)) 57 | grid := make(map[point.Point]Cell, (width * height)) 58 | var exits []point.Point 59 | 60 | for len(live) != 0 { 61 | i := chooser.Choose(len(live)) 62 | p := live[i] 63 | visited[p] = struct{}{} 64 | 65 | dead := true 66 | for _, i := range r.Perm(4) { 67 | d := directions[i] 68 | n := p.AddDirection(d) 69 | 70 | if !n.Inside(minBounds, maxBounds) { 71 | continue 72 | } 73 | 74 | if _, ok := visited[n]; ok { 75 | continue 76 | } 77 | 78 | dead = false 79 | 80 | c, ok := grid[p] 81 | if !ok { 82 | c = Cell{ 83 | Next: []direction.Direction{}, 84 | } 85 | } 86 | c.Next = append(c.Next, d) 87 | grid[p] = c 88 | 89 | grid[n] = Cell{ 90 | Next: []direction.Direction{direction.Opposite(d)}, 91 | } 92 | 93 | live = append(live, n) 94 | break 95 | } 96 | 97 | if dead { 98 | live = append(live[:i], live[i+1:]...) 99 | if len(grid[p].Next) == 1 { 100 | exits = append(exits, p) 101 | } 102 | } 103 | } 104 | 105 | ret.Exit = exits[r.Intn(len(exits))] 106 | ret.Grid = grid 107 | return ret 108 | } 109 | -------------------------------------------------------------------------------- /services/maze/generator_test.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkGenerateBacktrack10x10(b *testing.B) { 9 | src := rand.New(rand.NewSource(0)) 10 | for i := 0; i < b.N; i++ { 11 | generate(10, 10, src, backtrack{}) 12 | } 13 | } 14 | 15 | func BenchmarkGenerateBacktrack100x100(b *testing.B) { 16 | src := rand.New(rand.NewSource(0)) 17 | for i := 0; i < b.N; i++ { 18 | generate(100, 100, src, backtrack{}) 19 | } 20 | } 21 | 22 | func BenchmarkGenerateBacktrack1000x1000(b *testing.B) { 23 | src := rand.New(rand.NewSource(0)) 24 | for i := 0; i < b.N; i++ { 25 | generate(1000, 1000, src, backtrack{}) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /services/maze/maze.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import ( 4 | "github.com/Aorioli/procedural/concerns/direction" 5 | "github.com/Aorioli/procedural/concerns/point" 6 | ) 7 | 8 | type Cell struct { 9 | Next []direction.Direction `json:"next"` 10 | } 11 | 12 | func (c Cell) Has(d direction.Direction) bool { 13 | for _, n := range c.Next { 14 | if n == d { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | type grid map[point.Point]Cell 22 | 23 | // Maze internal structure 24 | type Maze struct { 25 | Width int `json:"width"` 26 | Height int `json:"height"` 27 | Grid grid `json:"grid"` 28 | Entrance point.Point `json:"entrance"` 29 | Exit point.Point `json:"exit"` 30 | } 31 | -------------------------------------------------------------------------------- /services/maze/maze_test.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Aorioli/procedural/concerns/direction" 7 | ) 8 | 9 | func TestHas(t *testing.T) { 10 | c := Cell{ 11 | Next: []direction.Direction{direction.North}, 12 | } 13 | for i, test := range []struct { 14 | d direction.Direction 15 | out bool 16 | }{ 17 | { 18 | d: direction.North, 19 | out: true, 20 | }, 21 | { 22 | d: direction.South, 23 | out: false, 24 | }, 25 | } { 26 | out := c.Has(test.d) 27 | if out != test.out { 28 | t.Errorf("Test %d: Expected %t, got %t", i, test.out, out) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /services/maze/service.go: -------------------------------------------------------------------------------- 1 | package maze 2 | 3 | import "math/rand" 4 | 5 | // Service interface exposes the generator service 6 | type Service interface { 7 | Generate(width, height int, seed int64, algorithm Chooser) Maze 8 | } 9 | 10 | type service struct{} 11 | 12 | // Middleware wraps the Service and performs actions before the implementation is called 13 | type Middleware func(Service) Service 14 | 15 | // New returns a Service implementation 16 | func New(middleware ...Middleware) Service { 17 | svc := Service(service{}) 18 | 19 | for _, m := range middleware { 20 | svc = m(svc) 21 | } 22 | 23 | return svc 24 | } 25 | 26 | func (s service) Generate(width, height int, seed int64, algorithm Chooser) Maze { 27 | return generate(width, height, rand.New(rand.NewSource(seed)), algorithm) 28 | } 29 | -------------------------------------------------------------------------------- /vendor/github.com/Aorioli/chopher/hasher/hasher.go: -------------------------------------------------------------------------------- 1 | package hasher 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/Aorioli/chopher/note" 7 | "github.com/Aorioli/chopher/scale" 8 | "github.com/Aorioli/chopher/song" 9 | ) 10 | 11 | var ( 12 | scaleMap = [...]scale.Pattern{ 13 | 0: scale.Major, 14 | 1: scale.Minor, 15 | 2: scale.Blues, 16 | 3: scale.Bebop, 17 | } 18 | 19 | durationMap = [...]note.Duration{ 20 | 0: note.Quarter, 21 | 1: note.Half, 22 | 2: note.Full, 23 | 3: note.Double, 24 | } 25 | ) 26 | 27 | type Hasher struct { 28 | r io.Reader 29 | Song *song.Song 30 | } 31 | 32 | func New(r io.Reader) Hasher { 33 | buf := make([]byte, 16) 34 | r.Read(buf) 35 | 36 | speed := song.Slow + song.Tempo(float64(buf[0])/256) 37 | scl := scaleMap[int(buf[1])%len(scaleMap)] 38 | var sum int 39 | for i := 1; i < len(buf); i++ { 40 | sum += int(buf[i]) 41 | } 42 | nt := note.Note{Note: note.C, Octave: 1}.AddHalfSteps(sum % 36) 43 | s := song.New(speed) 44 | s.Scale = scl.New(nt, buf[2]%2 == 0) 45 | 46 | return Hasher{ 47 | Song: &s, 48 | r: r, 49 | } 50 | } 51 | 52 | func (h *Hasher) Hash() *song.Song { 53 | io.Copy(h, h.r) 54 | songLength := len(h.Song.Notes) 55 | if songLength > 200 { 56 | h.Song.Notes = h.Song.Notes[:200] 57 | } 58 | songLength = len(h.Song.Notes) 59 | if songLength > 1 { 60 | h.Song.Notes[songLength-1].Duration = note.Full + 1.0 61 | } 62 | return h.Song 63 | } 64 | 65 | func (h *Hasher) Write(p []byte) (int, error) { 66 | for i := 1; i < len(p); i = i + 2 { 67 | add := h.Song.Scale.Notes[int(p[i-1])%len(h.Song.Scale.Notes)] 68 | h.Song = h.Song.Add(add, durationMap[int(p[i])%len(durationMap)]) 69 | } 70 | return len(p), nil 71 | } 72 | -------------------------------------------------------------------------------- /vendor/github.com/Aorioli/chopher/karplus/karplus.go: -------------------------------------------------------------------------------- 1 | package karplus 2 | 3 | import ( 4 | "io" 5 | "math" 6 | "math/rand" 7 | 8 | "github.com/Aorioli/chopher/note" 9 | "github.com/Aorioli/chopher/song" 10 | ) 11 | 12 | type Note struct { 13 | Note song.SongNote 14 | Buffer []float64 15 | } 16 | 17 | func NewNote(n song.SongNote, samplingRate int) *Note { 18 | buf := make([]float64, int( 19 | math.Ceil( 20 | float64(samplingRate)/n.Note.Frequency(), 21 | ), 22 | )) 23 | 24 | for i := 0; i < len(buf); i++ { 25 | buf[i] = rand.Float64()*2.0 - 1.0 26 | } 27 | return &Note{ 28 | Note: n, 29 | Buffer: buf, 30 | } 31 | } 32 | 33 | // Sound pops the current buffer value and appends the new one 34 | func (n *Note) Sound() float64 { 35 | if n.Note.Note.Note == note.Rest { 36 | return 0 37 | } 38 | sampleValue := n.Buffer[0] 39 | 40 | v := (n.Buffer[0] + n.Buffer[1]) * 0.5 * 0.9999 41 | n.Buffer = append(n.Buffer[1:], v) 42 | 43 | return sampleValue 44 | } 45 | 46 | type Song struct { 47 | Song song.Song 48 | SamplingRate int 49 | CurrentNotes []*Note 50 | } 51 | 52 | func (s *Song) Sound(w io.Writer) { 53 | var lastNote int 54 | for i, n := range s.Song.Notes { 55 | if n.IsValid(0) { 56 | s.CurrentNotes = append(s.CurrentNotes, NewNote(n, s.SamplingRate)) 57 | lastNote = i 58 | } 59 | } 60 | 61 | var ( 62 | time float64 63 | increment = float64(s.Song.Tempo) / float64(s.SamplingRate) 64 | ) 65 | 66 | temp := make([]*Note, 0, len(s.Song.Notes)/10+1) 67 | for len(s.CurrentNotes) > 0 { 68 | var sample float64 69 | temp = make([]*Note, 0, len(s.Song.Notes)/10+1) 70 | for _, n := range s.CurrentNotes { 71 | sample += n.Sound() 72 | if n.Note.IsValid(time) { 73 | temp = append(temp, n) 74 | } 75 | } 76 | 77 | sampleValue := int16(sample * 2048) 78 | w.Write([]byte{byte(sampleValue), byte(sampleValue >> 8)}) 79 | time += increment 80 | 81 | for i := lastNote + 1; i < len(s.Song.Notes); i++ { 82 | n := s.Song.Notes[i] 83 | if n.IsValid(time) { 84 | temp = append(temp, NewNote(n, s.SamplingRate)) 85 | lastNote = i 86 | } 87 | } 88 | 89 | s.CurrentNotes = temp 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /vendor/github.com/Aorioli/chopher/note/note.go: -------------------------------------------------------------------------------- 1 | package note 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | const ( 9 | a4Frequency float64 = 440.0 10 | magic float64 = 1.0594630943592952645618252949463417007792043174941856 11 | Double Duration = 2 12 | Full Duration = 1 13 | Half Duration = 0.5 14 | Quarter Duration = 0.25 15 | ) 16 | 17 | // Duration value 18 | type Duration float64 19 | 20 | // Musical scale halfstep values 21 | const ( 22 | C = iota 23 | CIS 24 | D 25 | DIS 26 | E 27 | F 28 | FIS 29 | G 30 | GIS 31 | A 32 | AIS 33 | B 34 | ) 35 | 36 | const ( 37 | Rest = 13 38 | ) 39 | 40 | var ( 41 | a4 = Note{ 42 | Note: A, 43 | Octave: 4, 44 | } 45 | ) 46 | 47 | // Note represents a piano key note with a combination of a note and it's Octave 48 | type Note struct { 49 | Note int 50 | Octave int 51 | } 52 | 53 | func New(note, octave int) Note { 54 | return Note{ 55 | Note: note, 56 | Octave: octave, 57 | } 58 | } 59 | 60 | // AddHalfSteps adds a number of half steps and returns a new note 61 | func (n Note) AddHalfSteps(hs int) Note { 62 | t := n.Note + hs 63 | if t >= 0 { 64 | n.Note = t % 12 65 | n.Octave = n.Octave + t/12 66 | } else { 67 | n.Note = B + t + 1 68 | n.Octave = n.Octave - 1 69 | } 70 | return n 71 | } 72 | 73 | // Frequency of the note 74 | // 75 | // https://en.wikipedia.org/wiki/Piano_key_frequencies 76 | func (n Note) Frequency() float64 { 77 | if n.Note == Rest { 78 | return 1.0 79 | } 80 | return a4Frequency * math.Pow(magic, float64(HalfstepDistance(a4, n))) 81 | } 82 | 83 | func HalfstepDistance(from, to Note) int { 84 | return (to.Octave-from.Octave)*12 + (to.Note - from.Note) 85 | } 86 | 87 | var Notes = [...]string{ 88 | "C", "C#", "D", "D#", 89 | "E", "F", "F#", "G", 90 | "G#", "A", "A#", "B", 91 | } 92 | 93 | func (n Note) String() string { 94 | return fmt.Sprintf("%s%d", Notes[n.Note], n.Octave) 95 | } 96 | -------------------------------------------------------------------------------- /vendor/github.com/Aorioli/chopher/scale/scale.go: -------------------------------------------------------------------------------- 1 | package scale 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Aorioli/chopher/note" 7 | ) 8 | 9 | type Chord []int 10 | 11 | func (c Chord) NotesInChord(base note.Note, check note.Note, pos int) bool { 12 | return pos < len(c) && note.HalfstepDistance(base, check) == c[pos] 13 | } 14 | 15 | // Pattern is an array of halfstep jumps 16 | type Pattern struct { 17 | Name string 18 | Scale []int 19 | Chords []Chord 20 | } 21 | 22 | var ( 23 | // Major scale pattern 24 | Major = Pattern{ 25 | Name: "Major", 26 | Scale: []int{2, 2, 1, 2, 2, 2, 1}, 27 | Chords: []Chord{ 28 | Chord{0, 4, 7, 11}, // seventh 29 | Chord{0, 4, 7}, // major 30 | Chord{0, 3, 7}, // minor 31 | Chord{0, 7}, // power 32 | }, 33 | } 34 | // Minor scale pattern 35 | Minor = Pattern{ 36 | Name: "Minor", 37 | Scale: []int{2, 1, 2, 2, 1, 2, 2}, 38 | Chords: []Chord{ 39 | Chord{0, 3, 7, 10}, // seventh 40 | Chord{0, 3, 7}, // minor 41 | Chord{0, 4, 7}, // major 42 | Chord{0, 3, 6}, // diminished 43 | }, 44 | } 45 | //Blues scale pattern 46 | Blues = Pattern{ 47 | Name: "Blues", 48 | Scale: []int{3, 2, 1, 1, 3, 2}, 49 | Chords: []Chord{ 50 | Chord{0, 4, 7, 10}, //dominant 51 | Chord{0, 3, 7}, // minor 52 | Chord{0, 3, 6}, // diminished 53 | Chord{0, 4, 7}, // major 54 | }, 55 | } 56 | //Bebop scale pattern 57 | Bebop = Pattern{ 58 | Name: "Bebop", 59 | Scale: []int{1, 1, 1, 2, 2, 1, 2}, 60 | Chords: []Chord{ 61 | Chord{0, 3, 7, 10}, // seventh 62 | Chord{0, 4, 7, 10}, //dominant 63 | Chord{0, 3, 7}, // minor 64 | Chord{0, 3, 6}, // diminished 65 | Chord{0, 4, 7}, // major 66 | }, 67 | } 68 | ) 69 | 70 | // Scale is defined by the notes and the pattern they form 71 | type Scale struct { 72 | Notes []note.Note 73 | Chords []Chord 74 | Key note.Note 75 | Pattern Pattern 76 | } 77 | 78 | // Scale turn a pattern to a scale using a key note 79 | func (p Pattern) New(key note.Note, reverse bool) Scale { 80 | n := make([]note.Note, len(p.Scale)+1) 81 | n[0] = key 82 | for i, v := range p.Scale { 83 | if reverse { 84 | v = -v 85 | } 86 | n[i+1] = n[i].AddHalfSteps(v) 87 | } 88 | 89 | return Scale{ 90 | Pattern: p, 91 | Key: key, 92 | Notes: n, 93 | Chords: p.Chords, 94 | } 95 | } 96 | 97 | func (s Scale) String() string { 98 | st := make([]string, len(s.Notes)) 99 | for i, n := range s.Notes { 100 | st[i] = n.String() 101 | } 102 | return strings.Join(st, "-") 103 | } 104 | -------------------------------------------------------------------------------- /vendor/github.com/Aorioli/chopher/song/song.go: -------------------------------------------------------------------------------- 1 | package song 2 | 3 | import ( 4 | "github.com/Aorioli/chopher/note" 5 | "github.com/Aorioli/chopher/scale" 6 | ) 7 | 8 | const ( 9 | Fast Tempo = 1.0 10 | Medium Tempo = 0.5 11 | Slow Tempo = 0.33 12 | ) 13 | 14 | // Tempo is the duration of the Full note in seconds 15 | // 16 | // Not my tempo!! 17 | type Tempo float64 18 | 19 | type SongNote struct { 20 | Note note.Note 21 | Duration note.Duration 22 | Start float64 23 | ChordBase bool 24 | } 25 | 26 | type Song struct { 27 | Tempo Tempo 28 | Notes []SongNote 29 | Scale scale.Scale 30 | } 31 | 32 | func New(t Tempo) Song { 33 | return Song{ 34 | Tempo: t, 35 | } 36 | } 37 | 38 | func (s *Song) add(note note.Note, duration note.Duration, start float64, base bool) *Song { 39 | s.Notes = append(s.Notes, SongNote{ 40 | Note: note, 41 | Duration: duration, 42 | Start: start, 43 | ChordBase: base, 44 | }) 45 | return s 46 | } 47 | 48 | func (s *Song) AddAfter(note note.Note, duration note.Duration) *Song { 49 | lastNote := SongNote{} 50 | if len(s.Notes) > 0 { 51 | lastNote = s.Notes[len(s.Notes)-1] 52 | } 53 | return s.add(note, duration, lastNote.Start+float64(lastNote.Duration), true) 54 | } 55 | 56 | func (s *Song) AddWith(note note.Note, duration note.Duration) *Song { 57 | lastNote := SongNote{} 58 | if len(s.Notes) > 0 { 59 | lastNote = s.Notes[len(s.Notes)-1] 60 | } 61 | return s.add(note, duration, lastNote.Start, false) 62 | } 63 | 64 | func (s *Song) Add(addNote note.Note, duration note.Duration) *Song { 65 | notel := len(s.Notes) 66 | if notel == 0 { 67 | return s.add(addNote, duration, 0, true) 68 | } 69 | 70 | for _, c := range s.Scale.Chords { 71 | if len(c) > notel+1 { 72 | c = c[:notel+1] 73 | } 74 | 75 | var ( 76 | base note.Note 77 | pos = notel 78 | ) 79 | 80 | for i := notel - 1; i >= 0; i-- { 81 | if s.Notes[i].ChordBase { 82 | base = s.Notes[i].Note 83 | pos = notel - i 84 | break 85 | } 86 | } 87 | if c.NotesInChord(base, addNote, pos) { 88 | return s.AddWith(addNote, duration) 89 | } 90 | } 91 | 92 | return s.AddAfter(addNote, duration) 93 | } 94 | 95 | func (s *SongNote) IsValid(time float64) bool { 96 | // log.Println(s.Note, time, s.Start, s.Start+float64(s.Duration)) 97 | if time < s.Start { 98 | return false 99 | } 100 | return time < (s.Start + float64(s.Duration)) 101 | } 102 | 103 | func min(a, b int) int { 104 | if a < b { 105 | return a 106 | } 107 | return b 108 | } 109 | -------------------------------------------------------------------------------- /vendor/github.com/Aorioli/chopher/wave/wave.go: -------------------------------------------------------------------------------- 1 | package wave 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | ) 9 | 10 | // channel is a wrapper for the number of channels in the wave file 11 | type channel uint16 12 | 13 | const ( 14 | riffID = "RIFF" 15 | riffType = "WAVE" 16 | fmtID = "fmt " 17 | fmtSize uint32 = 0x10 // fmt size is constant 18 | fmtCompressionCode uint16 = 1 19 | // Mono channel 20 | Mono channel = 1 21 | // Stereo channel 22 | Stereo channel = 2 23 | dataID = "data" 24 | ) 25 | 26 | type chunkHeader struct { 27 | id string 28 | size uint32 29 | } 30 | 31 | func (c chunkHeader) write(w io.Writer) { 32 | binary.Write(w, binary.LittleEndian, []byte(c.id)) 33 | binary.Write(w, binary.LittleEndian, c.size) 34 | } 35 | 36 | type fmtChunk struct { 37 | chunkHeader 38 | CompressionCode uint16 39 | Channels channel 40 | SampleRate uint32 41 | BytesPerSecond uint32 42 | BytesPerSample uint16 43 | BitsPerSample uint16 44 | } 45 | 46 | func (f fmtChunk) write(w io.Writer) { 47 | f.chunkHeader.write(w) 48 | binary.Write(w, binary.LittleEndian, f.CompressionCode) 49 | binary.Write(w, binary.LittleEndian, f.Channels) 50 | binary.Write(w, binary.LittleEndian, f.SampleRate) 51 | binary.Write(w, binary.LittleEndian, f.BytesPerSecond) 52 | binary.Write(w, binary.LittleEndian, f.BytesPerSample) 53 | binary.Write(w, binary.LittleEndian, f.BitsPerSample) 54 | } 55 | 56 | type dataChunk struct { 57 | chunkHeader 58 | Bytes []byte 59 | } 60 | 61 | func (d dataChunk) write(w io.Writer) { 62 | d.chunkHeader.write(w) 63 | binary.Write(w, binary.LittleEndian, d.Bytes) 64 | } 65 | 66 | // Wave struct represents the format of the wave file 67 | // Wave implements io.Writer for writing to the underlying Data array 68 | type Wave struct { 69 | chunkHeader 70 | riffType string 71 | format fmtChunk 72 | Data dataChunk 73 | } 74 | 75 | // New creates a new Wave with calculated field values for the format 76 | func New(channels channel, sampleRate uint32) Wave { 77 | w := Wave{ 78 | chunkHeader: chunkHeader{ 79 | id: riffID, 80 | }, 81 | riffType: riffType, 82 | format: fmtChunk{ 83 | chunkHeader: chunkHeader{ 84 | id: fmtID, 85 | size: fmtSize, 86 | }, 87 | CompressionCode: fmtCompressionCode, 88 | Channels: channels, 89 | SampleRate: sampleRate, 90 | BytesPerSecond: sampleRate * uint32(channels), 91 | BytesPerSample: uint16(channels), 92 | BitsPerSample: uint16(channels) * 8, 93 | }, 94 | Data: dataChunk{ 95 | chunkHeader: chunkHeader{ 96 | id: dataID, 97 | }, 98 | }, 99 | } 100 | return w 101 | } 102 | 103 | // Reader return the entire wave struct in a byte buffer 104 | // encoded in little endian 105 | func (w Wave) Reader() io.Reader { 106 | w.Data.size = uint32(len(w.Data.Bytes)) 107 | w.size = w.Data.size + w.format.size + 4 108 | 109 | var b bytes.Buffer 110 | 111 | w.chunkHeader.write(&b) 112 | binary.Write(&b, binary.LittleEndian, []byte(w.riffType)) 113 | w.format.write(&b) 114 | w.Data.write(&b) 115 | return &b 116 | } 117 | 118 | func (w *Wave) Write(p []byte) (int, error) { 119 | if len(p)%int(w.format.BytesPerSample) != 0 { 120 | return 0, errors.New("The given array doesn't match the file format") 121 | } 122 | 123 | w.Data.Bytes = append(w.Data.Bytes, p...) 124 | return len(p), nil 125 | } 126 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Peter Bourgon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/endpoint/endpoint.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | "errors" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // Endpoint is the fundamental building block of servers and clients. 10 | // It represents a single RPC method. 11 | type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error) 12 | 13 | // Middleware is a chainable behavior modifier for endpoints. 14 | type Middleware func(Endpoint) Endpoint 15 | 16 | // ErrBadCast indicates an unexpected concrete request or response struct was 17 | // received from an endpoint. 18 | var ErrBadCast = errors.New("bad cast") 19 | 20 | // ContextCanceled indicates the request context was canceled. 21 | var ErrContextCanceled = errors.New("context canceled") 22 | 23 | // Chain is a helper function for composing middlewares. Requests will 24 | // traverse them in the order they're declared. That is, the first middleware 25 | // is treated as the outermost middleware. 26 | func Chain(outer Middleware, others ...Middleware) Middleware { 27 | return func(next Endpoint) Endpoint { 28 | for i := len(others) - 1; i >= 0; i-- { // reverse 29 | next = others[i](next) 30 | } 31 | return outer(next) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/README.md: -------------------------------------------------------------------------------- 1 | # package log 2 | 3 | `package log` provides a minimal interface for structured logging in services. 4 | It may be wrapped to encode conventions, enforce type-safety, etc. 5 | It can be used for both typical application log events, and log-structured data streams. 6 | 7 | ## Rationale 8 | 9 | TODO 10 | 11 | ## Usage 12 | 13 | Typical application logging. 14 | 15 | ```go 16 | import ( 17 | "os" 18 | 19 | "github.com/go-kit/kit/log" 20 | ) 21 | 22 | func main() { 23 | logger := log.NewLogfmtLogger(os.Stderr) 24 | logger.Log("question", "what is the meaning of life?", "answer", 42) 25 | } 26 | ``` 27 | 28 | Output: 29 | ``` 30 | question="what is the meaning of life?" answer=42 31 | ``` 32 | 33 | Contextual logging. 34 | 35 | ```go 36 | func handle(logger log.Logger, req *Request) { 37 | ctx := log.NewContext(logger).With("txid", req.TransactionID, "query", req.Query) 38 | ctx.Log() 39 | 40 | answer, err := process(ctx, req.Query) 41 | if err != nil { 42 | ctx.Log("err", err) 43 | return 44 | } 45 | 46 | ctx.Log("answer", answer) 47 | } 48 | ``` 49 | 50 | Output: 51 | ``` 52 | txid=12345 query="some=query" 53 | txid=12345 query="some=query" answer=42 54 | ``` 55 | 56 | Redirect stdlib log to gokit logger. 57 | 58 | ```go 59 | import ( 60 | "os" 61 | stdlog "log" 62 | kitlog "github.com/go-kit/kit/log" 63 | ) 64 | 65 | func main() { 66 | logger := kitlog.NewJSONLogger(os.Stdout) 67 | stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) 68 | 69 | stdlog.Print("I sure like pie") 70 | } 71 | ``` 72 | 73 | Output 74 | ``` 75 | {"msg":"I sure like pie","ts":"2016/01/28 19:41:08"} 76 | ``` 77 | 78 | Adding a timestamp to contextual logs 79 | 80 | ```go 81 | func handle(logger log.Logger, req *Request) { 82 | ctx := log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "query", req.Query) 83 | ctx.Log() 84 | 85 | answer, err := process(ctx, req.Query) 86 | if err != nil { 87 | ctx.Log("err", err) 88 | return 89 | } 90 | 91 | ctx.Log("answer", answer) 92 | } 93 | ``` 94 | 95 | Output 96 | ``` 97 | ts=2016-01-29T00:46:04Z query="some=query" 98 | ts=2016-01-29T00:46:04Z query="some=query" answer=42 99 | ``` 100 | 101 | Adding caller info to contextual logs 102 | 103 | ```go 104 | func handle(logger log.Logger, req *Request) { 105 | ctx := log.NewContext(logger).With("caller", log.DefaultCaller, "query", req.Query) 106 | ctx.Log() 107 | 108 | answer, err := process(ctx, req.Query) 109 | if err != nil { 110 | ctx.Log("err", err) 111 | return 112 | } 113 | 114 | ctx.Log("answer", answer) 115 | } 116 | ``` 117 | 118 | Output 119 | ``` 120 | caller=logger.go:20 query="some=query" 121 | caller=logger.go:28 query="some=query" answer=42 122 | ``` 123 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/json_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | ) 10 | 11 | type jsonLogger struct { 12 | io.Writer 13 | } 14 | 15 | // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a 16 | // single JSON object. 17 | func NewJSONLogger(w io.Writer) Logger { 18 | return &jsonLogger{w} 19 | } 20 | 21 | func (l *jsonLogger) Log(keyvals ...interface{}) error { 22 | n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd 23 | m := make(map[string]interface{}, n) 24 | for i := 0; i < len(keyvals); i += 2 { 25 | k := keyvals[i] 26 | var v interface{} = ErrMissingValue 27 | if i+1 < len(keyvals) { 28 | v = keyvals[i+1] 29 | } 30 | merge(m, k, v) 31 | } 32 | return json.NewEncoder(l.Writer).Encode(m) 33 | } 34 | 35 | func merge(dst map[string]interface{}, k, v interface{}) { 36 | var key string 37 | switch x := k.(type) { 38 | case string: 39 | key = x 40 | case fmt.Stringer: 41 | key = safeString(x) 42 | default: 43 | key = fmt.Sprint(x) 44 | } 45 | if x, ok := v.(error); ok { 46 | v = safeError(x) 47 | } 48 | 49 | // We want json.Marshaler and encoding.TextMarshaller to take priority over 50 | // err.Error() and v.String(). But json.Marshall (called later) does that by 51 | // default so we force a no-op if it's one of those 2 case. 52 | switch x := v.(type) { 53 | case json.Marshaler: 54 | case encoding.TextMarshaler: 55 | case error: 56 | v = safeError(x) 57 | case fmt.Stringer: 58 | v = safeString(x) 59 | } 60 | 61 | dst[key] = v 62 | } 63 | 64 | func safeString(str fmt.Stringer) (s string) { 65 | defer func() { 66 | if panicVal := recover(); panicVal != nil { 67 | if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { 68 | s = "NULL" 69 | } else { 70 | panic(panicVal) 71 | } 72 | } 73 | }() 74 | s = str.String() 75 | return 76 | } 77 | 78 | func safeError(err error) (s interface{}) { 79 | defer func() { 80 | if panicVal := recover(); panicVal != nil { 81 | if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { 82 | s = nil 83 | } else { 84 | panic(panicVal) 85 | } 86 | } 87 | }() 88 | s = err.Error() 89 | return 90 | } 91 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/log.go: -------------------------------------------------------------------------------- 1 | // Package log provides basic interfaces for structured logging. 2 | // 3 | // The fundamental interface is Logger. Loggers create log events from 4 | // key/value data. 5 | package log 6 | 7 | import ( 8 | "errors" 9 | "sync/atomic" 10 | ) 11 | 12 | // Logger is the fundamental interface for all log operations. Log creates a 13 | // log event from keyvals, a variadic sequence of alternating keys and values. 14 | // Implementations must be safe for concurrent use by multiple goroutines. In 15 | // particular, any implementation of Logger that appends to keyvals or 16 | // modifies any of its elements must make a copy first. 17 | type Logger interface { 18 | Log(keyvals ...interface{}) error 19 | } 20 | 21 | // ErrMissingValue is appended to keyvals slices with odd length to substitute 22 | // the missing value. 23 | var ErrMissingValue = errors.New("(MISSING)") 24 | 25 | // NewContext returns a new Context that logs to logger. 26 | func NewContext(logger Logger) *Context { 27 | if c, ok := logger.(*Context); ok { 28 | return c 29 | } 30 | return &Context{logger: logger} 31 | } 32 | 33 | // Context must always have the same number of stack frames between calls to 34 | // its Log method and the eventual binding of Valuers to their value. This 35 | // requirement comes from the functional requirement to allow a context to 36 | // resolve application call site information for a log.Caller stored in the 37 | // context. To do this we must be able to predict the number of logging 38 | // functions on the stack when bindValues is called. 39 | // 40 | // Three implementation details provide the needed stack depth consistency. 41 | // The first two of these details also result in better amortized performance, 42 | // and thus make sense even without the requirements regarding stack depth. 43 | // The third detail, however, is subtle and tied to the implementation of the 44 | // Go compiler. 45 | // 46 | // 1. NewContext avoids introducing an additional layer when asked to 47 | // wrap another Context. 48 | // 2. With avoids introducing an additional layer by returning a newly 49 | // constructed Context with a merged keyvals rather than simply 50 | // wrapping the existing Context. 51 | // 3. All of Context's methods take pointer receivers even though they 52 | // do not mutate the Context. 53 | // 54 | // Before explaining the last detail, first some background. The Go compiler 55 | // generates wrapper methods to implement the auto dereferencing behavior when 56 | // calling a value method through a pointer variable. These wrapper methods 57 | // are also used when calling a value method through an interface variable 58 | // because interfaces store a pointer to the underlying concrete value. 59 | // Calling a pointer receiver through an interface does not require generating 60 | // an additional function. 61 | // 62 | // If Context had value methods then calling Context.Log through a variable 63 | // with type Logger would have an extra stack frame compared to calling 64 | // Context.Log through a variable with type Context. Using pointer receivers 65 | // avoids this problem. 66 | 67 | // A Context wraps a Logger and holds keyvals that it includes in all log 68 | // events. When logging, a Context replaces all value elements (odd indexes) 69 | // containing a Valuer with their generated value for each call to its Log 70 | // method. 71 | type Context struct { 72 | logger Logger 73 | keyvals []interface{} 74 | hasValuer bool 75 | } 76 | 77 | // Log replaces all value elements (odd indexes) containing a Valuer in the 78 | // stored context with their generated value, appends keyvals, and passes the 79 | // result to the wrapped Logger. 80 | func (l *Context) Log(keyvals ...interface{}) error { 81 | kvs := append(l.keyvals, keyvals...) 82 | if len(kvs)%2 != 0 { 83 | kvs = append(kvs, ErrMissingValue) 84 | } 85 | if l.hasValuer { 86 | // If no keyvals were appended above then we must copy l.keyvals so 87 | // that future log events will reevaluate the stored Valuers. 88 | if len(keyvals) == 0 { 89 | kvs = append([]interface{}{}, l.keyvals...) 90 | } 91 | bindValues(kvs[:len(l.keyvals)]) 92 | } 93 | return l.logger.Log(kvs...) 94 | } 95 | 96 | // With returns a new Context with keyvals appended to those of the receiver. 97 | func (l *Context) With(keyvals ...interface{}) *Context { 98 | if len(keyvals) == 0 { 99 | return l 100 | } 101 | kvs := append(l.keyvals, keyvals...) 102 | if len(kvs)%2 != 0 { 103 | kvs = append(kvs, ErrMissingValue) 104 | } 105 | return &Context{ 106 | logger: l.logger, 107 | // Limiting the capacity of the stored keyvals ensures that a new 108 | // backing array is created if the slice must grow in Log or With. 109 | // Using the extra capacity without copying risks a data race that 110 | // would violate the Logger interface contract. 111 | keyvals: kvs[:len(kvs):len(kvs)], 112 | hasValuer: l.hasValuer || containsValuer(keyvals), 113 | } 114 | } 115 | 116 | // WithPrefix returns a new Context with keyvals prepended to those of the 117 | // receiver. 118 | func (l *Context) WithPrefix(keyvals ...interface{}) *Context { 119 | if len(keyvals) == 0 { 120 | return l 121 | } 122 | // Limiting the capacity of the stored keyvals ensures that a new 123 | // backing array is created if the slice must grow in Log or With. 124 | // Using the extra capacity without copying risks a data race that 125 | // would violate the Logger interface contract. 126 | n := len(l.keyvals) + len(keyvals) 127 | if len(keyvals)%2 != 0 { 128 | n++ 129 | } 130 | kvs := make([]interface{}, 0, n) 131 | kvs = append(kvs, keyvals...) 132 | if len(kvs)%2 != 0 { 133 | kvs = append(kvs, ErrMissingValue) 134 | } 135 | kvs = append(kvs, l.keyvals...) 136 | return &Context{ 137 | logger: l.logger, 138 | keyvals: kvs, 139 | hasValuer: l.hasValuer || containsValuer(keyvals), 140 | } 141 | } 142 | 143 | // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If 144 | // f is a function with the appropriate signature, LoggerFunc(f) is a Logger 145 | // object that calls f. 146 | type LoggerFunc func(...interface{}) error 147 | 148 | // Log implements Logger by calling f(keyvals...). 149 | func (f LoggerFunc) Log(keyvals ...interface{}) error { 150 | return f(keyvals...) 151 | } 152 | 153 | // SwapLogger wraps another logger that may be safely replaced while other 154 | // goroutines use the SwapLogger concurrently. The zero value for a SwapLogger 155 | // will discard all log events without error. 156 | // 157 | // SwapLogger serves well as a package global logger that can be changed by 158 | // importers. 159 | type SwapLogger struct { 160 | logger atomic.Value 161 | } 162 | 163 | type loggerStruct struct { 164 | Logger 165 | } 166 | 167 | // Log implements the Logger interface by forwarding keyvals to the currently 168 | // wrapped logger. It does not log anything if the wrapped logger is nil. 169 | func (l *SwapLogger) Log(keyvals ...interface{}) error { 170 | s, ok := l.logger.Load().(loggerStruct) 171 | if !ok || s.Logger == nil { 172 | return nil 173 | } 174 | return s.Log(keyvals...) 175 | } 176 | 177 | // Swap replaces the currently wrapped logger with logger. Swap may be called 178 | // concurrently with calls to Log from other goroutines. 179 | func (l *SwapLogger) Swap(logger Logger) { 180 | l.logger.Store(loggerStruct{logger}) 181 | } 182 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/logfmt_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | 6 | "gopkg.in/logfmt.v0" 7 | ) 8 | 9 | type logfmtLogger struct { 10 | w io.Writer 11 | } 12 | 13 | // NewLogfmtLogger returns a logger that encodes keyvals to the Writer in 14 | // logfmt format. The passed Writer must be safe for concurrent use by 15 | // multiple goroutines if the returned Logger will be used concurrently. 16 | func NewLogfmtLogger(w io.Writer) Logger { 17 | return &logfmtLogger{w} 18 | } 19 | 20 | func (l logfmtLogger) Log(keyvals ...interface{}) error { 21 | // The Logger interface requires implementations to be safe for concurrent 22 | // use by multiple goroutines. For this implementation that means making 23 | // only one call to l.w.Write() for each call to Log. We first collect all 24 | // of the bytes into b, and then call l.w.Write(b). 25 | b, err := logfmt.MarshalKeyvals(keyvals...) 26 | if err != nil { 27 | return err 28 | } 29 | b = append(b, '\n') 30 | if _, err := l.w.Write(b); err != nil { 31 | return err 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/nop_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type nopLogger struct{} 4 | 5 | // NewNopLogger returns a logger that doesn't do anything. 6 | func NewNopLogger() Logger { return nopLogger{} } 7 | 8 | func (nopLogger) Log(...interface{}) error { return nil } 9 | -------------------------------------------------------------------------------- /vendor/github.com/go-kit/kit/log/stdlib.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | // StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's 11 | // designed to be passed to a Go kit logger as the writer, for cases where 12 | // it's necessary to redirect all Go kit log output to the stdlib logger. 13 | // 14 | // If you have any choice in the matter, you shouldn't use this. Prefer to 15 | // redirect the stdlib log to the Go kit logger via NewStdlibAdapter. 16 | type StdlibWriter struct{} 17 | 18 | // Write implements io.Writer. 19 | func (w StdlibWriter) Write(p []byte) (int, error) { 20 | log.Print(strings.TrimSpace(string(p))) 21 | return len(p), nil 22 | } 23 | 24 | // StdlibAdapter wraps a Logger and allows it to be passed to the stdlib 25 | // logger's SetOutput. It will extract date/timestamps, filenames, and 26 | // messages, and place them under relevant keys. 27 | type StdlibAdapter struct { 28 | Logger 29 | timestampKey string 30 | fileKey string 31 | messageKey string 32 | } 33 | 34 | // StdlibAdapterOption sets a parameter for the StdlibAdapter. 35 | type StdlibAdapterOption func(*StdlibAdapter) 36 | 37 | // TimestampKey sets the key for the timestamp field. By default, it's "ts". 38 | func TimestampKey(key string) StdlibAdapterOption { 39 | return func(a *StdlibAdapter) { a.timestampKey = key } 40 | } 41 | 42 | // FileKey sets the key for the file and line field. By default, it's "file". 43 | func FileKey(key string) StdlibAdapterOption { 44 | return func(a *StdlibAdapter) { a.fileKey = key } 45 | } 46 | 47 | // MessageKey sets the key for the actual log message. By default, it's "msg". 48 | func MessageKey(key string) StdlibAdapterOption { 49 | return func(a *StdlibAdapter) { a.messageKey = key } 50 | } 51 | 52 | // NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed 53 | // logger. It's designed to be passed to log.SetOutput. 54 | func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { 55 | a := StdlibAdapter{ 56 | Logger: logger, 57 | timestampKey: "ts", 58 | fileKey: "file", 59 | messageKey: "msg", 60 | } 61 | for _, option := range options { 62 | option(&a) 63 | } 64 | return a 65 | } 66 | 67 | func (a StdlibAdapter) Write(p []byte) (int, error) { 68 | result := subexps(p) 69 | keyvals := []interface{}{} 70 | var timestamp string 71 | if date, ok := result["date"]; ok && date != "" { 72 | timestamp = date 73 | } 74 | if time, ok := result["time"]; ok && time != "" { 75 | if timestamp != "" { 76 | timestamp += " " 77 | } 78 | timestamp += time 79 | } 80 | if timestamp != "" { 81 | keyvals = append(keyvals, a.timestampKey, timestamp) 82 | } 83 | if file, ok := result["file"]; ok && file != "" { 84 | keyvals = append(keyvals, a.fileKey, file) 85 | } 86 | if msg, ok := result["msg"]; ok { 87 | keyvals = append(keyvals, a.messageKey, msg) 88 | } 89 | if err := a.Logger.Log(keyvals...); err != nil { 90 | return 0, err 91 | } 92 | return len(p), nil 93 | } 94 | 95 | const ( 96 | logRegexpDate = `(?P[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?` 97 | logRegexpTime = `(?P