Skip to content

Commit f02d405

Browse files
committed
broken: Split UserScreen into PipedScreen, BufferedScreen, HandledScreen
1 parent 2951ace commit f02d405

File tree

8 files changed

+263
-232
lines changed

8 files changed

+263
-232
lines changed

chat/message/config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package message
2+
3+
import "regexp"
4+
5+
// Container for per-user configurations.
6+
type UserConfig struct {
7+
Highlight *regexp.Regexp
8+
Bell bool
9+
Quiet bool
10+
Theme *Theme
11+
Seed int
12+
}
13+
14+
// Default user configuration to use
15+
var DefaultUserConfig UserConfig
16+
17+
func init() {
18+
DefaultUserConfig = UserConfig{
19+
Bell: true,
20+
Quiet: false,
21+
}
22+
23+
// TODO: Seed random?
24+
}

chat/message/screen.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package message
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"math/rand"
8+
"regexp"
9+
"sync"
10+
"time"
11+
)
12+
13+
var ErrUserClosed = errors.New("user closed")
14+
15+
const messageBuffer = 5
16+
const messageTimeout = 5 * time.Second
17+
18+
func BufferedScreen(name string, screen io.WriteCloser) *bufferedScreen {
19+
return &bufferedScreen{
20+
pipedScreen: PipedScreen(name, screen),
21+
msg: make(chan Message, messageBuffer),
22+
done: make(chan struct{}),
23+
}
24+
}
25+
26+
func PipedScreen(name string, screen io.WriteCloser) *pipedScreen {
27+
return &pipedScreen{
28+
baseScreen: &baseScreen{
29+
User: NewUser(name),
30+
},
31+
WriteCloser: screen,
32+
}
33+
}
34+
35+
func HandledScreen(name string, handler func(Message) error) *handledScreen {
36+
return &handledScreen{
37+
baseScreen: &baseScreen{
38+
User: NewUser(name),
39+
},
40+
handler: handler,
41+
}
42+
}
43+
44+
type handledScreen struct {
45+
*baseScreen
46+
handler func(Message) error
47+
}
48+
49+
func (u *handledScreen) Send(m Message) error {
50+
return u.handler(m)
51+
}
52+
53+
// Screen that pipes messages to an io.WriteCloser
54+
type pipedScreen struct {
55+
*baseScreen
56+
io.WriteCloser
57+
}
58+
59+
func (u *pipedScreen) Send(m Message) error {
60+
r := u.render(m)
61+
_, err := u.Write([]byte(r))
62+
if err != nil {
63+
logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
64+
u.Close()
65+
}
66+
return err
67+
}
68+
69+
// User container that knows about writing to an IO screen.
70+
type baseScreen struct {
71+
sync.Mutex
72+
*User
73+
}
74+
75+
func (u *baseScreen) Config() UserConfig {
76+
u.Lock()
77+
defer u.Unlock()
78+
return u.config
79+
}
80+
81+
func (u *baseScreen) SetConfig(cfg UserConfig) {
82+
u.Lock()
83+
u.config = cfg
84+
u.Unlock()
85+
}
86+
87+
func (u *baseScreen) ID() string {
88+
u.Lock()
89+
defer u.Unlock()
90+
return SanitizeName(u.name)
91+
}
92+
93+
func (u *baseScreen) Name() string {
94+
u.Lock()
95+
defer u.Unlock()
96+
return u.name
97+
}
98+
99+
func (u *baseScreen) Joined() time.Time {
100+
return u.joined
101+
}
102+
103+
// Rename the user with a new Identifier.
104+
func (u *baseScreen) SetName(name string) {
105+
u.Lock()
106+
u.name = name
107+
u.config.Seed = rand.Int()
108+
u.Unlock()
109+
}
110+
111+
// ReplyTo returns the last user that messaged this user.
112+
func (u *baseScreen) ReplyTo() Author {
113+
u.Lock()
114+
defer u.Unlock()
115+
return u.replyTo
116+
}
117+
118+
// SetReplyTo sets the last user to message this user.
119+
func (u *baseScreen) SetReplyTo(user Author) {
120+
// TODO: Use UserConfig.ReplyTo string
121+
u.Lock()
122+
defer u.Unlock()
123+
u.replyTo = user
124+
}
125+
126+
// SetHighlight sets the highlighting regular expression to match string.
127+
func (u *baseScreen) SetHighlight(s string) error {
128+
re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
129+
if err != nil {
130+
return err
131+
}
132+
u.Lock()
133+
u.config.Highlight = re
134+
u.Unlock()
135+
return nil
136+
}
137+
138+
func (u *baseScreen) render(m Message) string {
139+
cfg := u.Config()
140+
switch m := m.(type) {
141+
case PublicMsg:
142+
return m.RenderFor(cfg) + Newline
143+
case PrivateMsg:
144+
u.SetReplyTo(m.From())
145+
return m.Render(cfg.Theme) + Newline
146+
default:
147+
return m.Render(cfg.Theme) + Newline
148+
}
149+
}
150+
151+
// Prompt renders a theme-colorized prompt string.
152+
func (u *baseScreen) Prompt() string {
153+
name := u.Name()
154+
cfg := u.Config()
155+
if cfg.Theme != nil {
156+
name = cfg.Theme.ColorName(u)
157+
}
158+
return fmt.Sprintf("[%s] ", name)
159+
}
160+
161+
// bufferedScreen is a screen that buffers messages on Send using a channel and a consuming goroutine.
162+
type bufferedScreen struct {
163+
*pipedScreen
164+
closeOnce sync.Once
165+
msg chan Message
166+
done chan struct{}
167+
}
168+
169+
func (u *bufferedScreen) Close() error {
170+
u.closeOnce.Do(func() {
171+
close(u.done)
172+
})
173+
174+
return u.pipedScreen.Close()
175+
}
176+
177+
// Add message to consume by user
178+
func (u *bufferedScreen) Send(m Message) error {
179+
select {
180+
case <-u.done:
181+
return ErrUserClosed
182+
case u.msg <- m:
183+
case <-time.After(messageTimeout):
184+
logger.Printf("Message buffer full, closing: %s", u.Name())
185+
u.Close()
186+
return ErrUserClosed
187+
}
188+
return nil
189+
}
190+
191+
// Consume message buffer into the handler. Will block, should be called in a
192+
// goroutine.
193+
func (u *bufferedScreen) Consume() {
194+
for {
195+
select {
196+
case <-u.done:
197+
return
198+
case m, ok := <-u.msg:
199+
if !ok {
200+
return
201+
}
202+
// Pass on to unbuffered screen.
203+
u.pipedScreen.Send(m)
204+
}
205+
}
206+
}

chat/message/theme_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func TestTheme(t *testing.T) {
5252
}
5353

5454
u := NewUser("foo")
55-
u.colorIdx = 4
55+
u.config.Seed = 4
5656
actual = colorTheme.ColorName(u)
5757
expected = "\033[38;05;5mfoo\033[0m"
5858
if actual != expected {

0 commit comments

Comments
 (0)