Browse Source

More sugar

Wijnand Modderman-Lenstra 2 years ago
parent
commit
2298f60ac9
3 changed files with 163 additions and 4 deletions
  1. 82
    0
      middleware/template/codec.go
  2. 58
    4
      middleware/template/template.go
  3. 23
    0
      middleware/template/yaml/yaml.go

+ 82
- 0
middleware/template/codec.go View File

@@ -0,0 +1,82 @@
1
+package template
2
+
3
+import (
4
+	"encoding/json"
5
+	"encoding/xml"
6
+	"errors"
7
+	"fmt"
8
+	"strings"
9
+	"sync"
10
+
11
+	phi "maze.io/phi.v1"
12
+)
13
+
14
+var (
15
+	codecs      = map[string]Codec{}
16
+	codecsMutex sync.RWMutex
17
+)
18
+
19
+type Codec interface {
20
+	Encode(phi.Context, interface{})
21
+}
22
+
23
+type jsonCodec struct{}
24
+
25
+func (c *jsonCodec) Encode(ctx phi.Context, v interface{}) {
26
+	ctx.Header().Set("Content-Type", "application/json")
27
+	enc := json.NewEncoder(ctx)
28
+	err := enc.Encode(v)
29
+	if err != nil {
30
+		ctx.Error(err)
31
+	}
32
+}
33
+
34
+type xmlCodec struct {
35
+	skipHeader bool
36
+}
37
+
38
+func (x *xmlCodec) Encode(ctx phi.Context, v interface{}) {
39
+	ctx.Header().Set("Content-Type", "application/xml")
40
+	if !x.skipHeader {
41
+		ctx.Write([]byte(xml.Header))
42
+	}
43
+	enc := xml.NewEncoder(ctx)
44
+	err := enc.Encode(v)
45
+	if err != nil {
46
+		ctx.Error(err)
47
+	}
48
+}
49
+
50
+// RegisterCodec registers an encoder for one or more content types.
51
+func RegisterCodec(codec Codec, contentTypes ...string) error {
52
+	codecsMutex.Lock()
53
+	defer codecsMutex.Unlock()
54
+
55
+	if codec == nil {
56
+		return errors.New(`phi: codec is nil`)
57
+	}
58
+
59
+	if len(contentTypes) == 0 {
60
+		return errors.New(`phi: no content type`)
61
+	}
62
+
63
+	for i, contentType := range contentTypes {
64
+		contentType = strings.ToLower(contentType)
65
+		contentTypes[i] = contentType
66
+
67
+		if _, dupe := codecs[contentType]; dupe {
68
+			return fmt.Errorf(`phi: duplicate codec for content type %s`, contentType)
69
+		}
70
+	}
71
+
72
+	for _, contentType := range contentTypes {
73
+		codecs[contentType] = codec
74
+	}
75
+
76
+	return nil
77
+}
78
+
79
+func init() {
80
+	RegisterCodec(new(jsonCodec), "application/json", "text/json", "json")
81
+	RegisterCodec(new(xmlCodec), "application/xml", "text/xml", "xml")
82
+}

+ 58
- 4
middleware/template/template.go View File

@@ -1,7 +1,9 @@
1 1
 package template
2 2
 
3 3
 import (
4
+	"log"
4 5
 	"net/http"
6
+	"strings"
5 7
 
6 8
 	phi "maze.io/phi.v1"
7 9
 
@@ -13,8 +15,8 @@ type Templater struct {
13 15
 	ts *pongo2.TemplateSet
14 16
 }
15 17
 
16
-// Template initialises a new template loader from disk.
17
-func Template(path string) (*Templater, error) {
18
+// New initialises a new template loader from disk.
19
+func New(path string) (*Templater, error) {
18 20
 	l, err := pongo2.NewLocalFileSystemLoader(path)
19 21
 	if err != nil {
20 22
 		return nil, err
@@ -22,18 +24,39 @@ func Template(path string) (*Templater, error) {
22 24
 	return &Templater{pongo2.NewSet("", l)}, nil
23 25
 }
24 26
 
27
+// Must calls New and panics if there is an error.
28
+func Must(path string) *Templater {
29
+	t, err := New(path)
30
+	if err != nil {
31
+		panic(err)
32
+	}
33
+	return t
34
+}
35
+
25 36
 // Context returns a context which can render templates.
26 37
 func (t *Templater) Context(ctx phi.Context) phi.Context {
27 38
 	return &Context{ctx, pongo2.Context{}, t.ts}
28 39
 }
29 40
 
30
-// ServeMiddleware injects the template function.
31
-func (t *Templater) ServeMiddleware(next phi.Handler) phi.Handler {
41
+// Middleware injects the template context.
42
+func (t *Templater) Middleware(next phi.Handler) phi.Handler {
32 43
 	return phi.HandlerFunc(func(ctx phi.Context) {
33 44
 		next.Serve(t.Context(ctx))
34 45
 	})
35 46
 }
36 47
 
48
+// Handler is an inline handler that converts to a template Context
49
+func (t *Templater) Handler(next func(*Context)) phi.Handler {
50
+	return t.HandlerFunc(next)
51
+}
52
+
53
+// HandlerFunc is an inline handler func that converts to a template Context
54
+func (t *Templater) HandlerFunc(next func(*Context)) phi.HandlerFunc {
55
+	return phi.HandlerFunc(func(ctx phi.Context) {
56
+		next(&Context{ctx, pongo2.Context{}, t.ts})
57
+	})
58
+}
59
+
37 60
 // Context is a phi Context suitable for use with templates. It adds the
38 61
 // context.Template function for rendering templates.
39 62
 type Context struct {
@@ -58,3 +81,34 @@ func (ctx *Context) Template(name string) {
58 81
 		ctx.Error(err, http.StatusInternalServerError)
59 82
 	}
60 83
 }
84
+
85
+func (ctx *Context) Render(contentType string, v ...interface{}) {
86
+	var data interface{}
87
+	switch len(v) {
88
+	case 0: // No argument provided, use context.Data
89
+		data = ctx.Data
90
+	case 1:
91
+		data = v[0]
92
+	default:
93
+		data = v
94
+	}
95
+
96
+	baseType := strings.Split(contentType, ";")[0]
97
+	if codec, ok := codecs[baseType]; ok {
98
+		codec.Encode(ctx, data)
99
+		return
100
+	}
101
+
102
+	log.Printf("phi: no codec for content type %s", contentType)
103
+	ctx.Error(nil)
104
+}
105
+
106
+// JSON renderer.
107
+func (ctx *Context) JSON(v ...interface{}) {
108
+	ctx.Render("application/json", v...)
109
+}
110
+
111
+// XML renderer.
112
+func (ctx *Context) XML(v ...interface{}) {
113
+	ctx.Render("application/xml", v...)
114
+}

+ 23
- 0
middleware/template/yaml/yaml.go View File

@@ -0,0 +1,23 @@
1
+package yaml
2
+
3
+import (
4
+	yaml "gopkg.in/yaml.v2"
5
+	phi "maze.io/phi.v1"
6
+	"maze.io/phi.v1/middleware/template"
7
+)
8
+
9
+type yamlCodec struct{}
10
+
11
+func (c *yamlCodec) Encode(ctx phi.Context, v interface{}) {
12
+	ctx.Header().Set("Content-Type", "application/x-yaml")
13
+	b, err := yaml.Marshal(v)
14
+	if err != nil {
15
+		ctx.Error(err)
16
+		return
17
+	}
18
+	ctx.Write(b)
19
+}
20
+
21
+func init() {
22
+	template.RegisterCodec(new(yamlCodec), "application/x-yaml", "application/yaml", "text/x-yaml", "text/yaml", "yaml")
23
+}

Loading…
Cancel
Save