-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathview.go
110 lines (98 loc) · 2.56 KB
/
view.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package view
import (
"bytes"
"fmt"
"io"
"sync"
)
// View represents a template, such as html/template or view/template
type View interface {
Execute(w io.Writer, data interface{}) error
}
// Manager is a simple view manager supporting sandboxed (pooled buffer) renders and runtime view registration/replacement
type Manager struct {
m *sync.RWMutex
buffers *bufferPool
views map[string]View
}
var ErrBufferPoolSizeInvalid = fmt.Errorf("view: bufferPoolSize should be greater than zero")
// New creates a new view Manager
//
// - bufferPoolSize should be greater than zero, typically 50 or more.
// - views represent the initial views registered and can be nil
func New(bufferPoolSize int, views map[string]View) *Manager {
if bufferPoolSize <= 0 {
panic(ErrBufferPoolSizeInvalid)
}
m := &Manager{
m: &sync.RWMutex{},
buffers: newBufferPool(bufferPoolSize),
views: make(map[string]View),
}
if views != nil {
for name, v := range views {
if v == nil {
panic(fmt.Errorf("view: View \"%s\" is nil", name))
}
m.views[name] = v
}
}
return m
}
// MustRegister registers or replaces a view with the name
//
// - v can't be nil
func (m *Manager) MustRegister(name string, v View) *Manager {
if v == nil {
panic(fmt.Errorf("view: View \"%s\" is nil", name))
}
m.m.Lock()
m.views[name] = v
m.m.Unlock()
return m
}
var ErrWriterRequired = fmt.Errorf("view: w can't be nil")
// Render renders the view to a pooled buffer instance and if no error occurred, writes to w
//
// - calling render with a name of a view that's not registered will return an error
// - w can't be nil
func (m *Manager) Render(name string, w io.Writer, data interface{}) error {
if w == nil {
return ErrWriterRequired
}
m.m.RLock()
v, ok := m.views[name]
m.m.RUnlock()
if !ok {
return fmt.Errorf("view: View \"%s\" doesn't exist", name)
}
b := m.buffers.Get()
// trade-off:
// when Render causes a panic, the buffer will not be reused
// but the runtime overhead of defer is avoided
err := v.Execute(b, data)
if err != nil {
m.buffers.Put(b)
return err
}
_, err = b.WriteTo(w)
m.buffers.Put(b)
return err
}
type bufferPool struct{ c chan *bytes.Buffer }
func newBufferPool(size int) (bp *bufferPool) { return &bufferPool{c: make(chan *bytes.Buffer, size)} }
func (bp *bufferPool) Get() (b *bytes.Buffer) {
select {
case b = <-bp.c: // reuse existing buffer
default:
b = bytes.NewBuffer([]byte{})
}
return
}
func (bp *bufferPool) Put(b *bytes.Buffer) {
b.Reset()
select {
case bp.c <- b:
default: // Discard the buffer if the pool is full.
}
}