-
Notifications
You must be signed in to change notification settings - Fork 147
/
registry.go
140 lines (114 loc) · 3.73 KB
/
registry.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package prompt
import (
"sync"
"github.com/pkg/errors"
"github.com/google/uuid"
)
// registryLock is the lock on the global prompter registry.
var registryLock sync.RWMutex
// registry is the global prompter registry.
var registry = make(map[string]chan Prompter)
// RegisterPrompter registers a prompter with the global registry. It generates
// a unique identifier for the prompter that can be used when requesting
// prompting.
func RegisterPrompter(prompter Prompter) (string, error) {
// Generate a unique identifier for this prompter.
randomUUID, err := uuid.NewRandom()
if err != nil {
return "", errors.Wrap(err, "unable to generate UUID for prompter")
}
identifier := randomUUID.String()
// Create and populate a channel ("holder") for passing the prompter around.
holder := make(chan Prompter, 1)
holder <- prompter
// Lock the registry for writing and defer its release.
registryLock.Lock()
defer registryLock.Unlock()
// Verify that the identifier isn't currently in use. This is an
// astronomically small possibility, though it could be indicative of a UUID
// implementation bug, so we at least watch for it and return an error if it
// occurs.
if _, ok := registry[identifier]; ok {
return "", errors.New("UUID collision")
}
// Register the holder.
registry[identifier] = holder
// Success.
return identifier, nil
}
// UnregisterPrompter unregisters a prompter from the global registry. If the
// prompter is not registered, this method panics. If a prompter is unregistered
// with prompts pending for it, they will be cancelled.
func UnregisterPrompter(identifier string) {
// Lock the registry for writing, grab the holder, and remove it from the
// registry. If it isn't currently registered, this must be a logic error.
registryLock.Lock()
holder, ok := registry[identifier]
if !ok {
panic("deregistration requested for unregistered prompter")
}
delete(registry, identifier)
registryLock.Unlock()
// Get the prompter back and close the holder to let anyone else who has it
// know that they won't be getting the prompter from it.
<-holder
close(holder)
}
// Message invokes the Message method on a prompter in the global registry. If
// the prompter identifier provided is an empty string, this method is a no-op
// and returns a nil error.
func Message(identifier, message string) error {
// If the prompter identifier is empty, don't do anything.
if identifier == "" {
return nil
}
// Grab the holder for the specified prompter. We only need a read lock on
// the registry for this purpose.
registryLock.RLock()
holder, ok := registry[identifier]
registryLock.RUnlock()
if !ok {
return errors.New("prompter not found")
}
// Acquire the prompter.
prompter, ok := <-holder
if !ok {
return errors.New("unable to acquire prompter")
}
// Perform messaging.
err := prompter.Message(message)
// Return the prompter to the holder.
holder <- prompter
// Handle errors.
if err != nil {
errors.Wrap(err, "unable to message")
}
// Success.
return nil
}
// Prompt invokes the Prompt method on a prompter in the global registry.
func Prompt(identifier, prompt string) (string, error) {
// Grab the holder for the specified prompter. We only need a read lock on
// the registry for this purpose.
registryLock.RLock()
holder, ok := registry[identifier]
registryLock.RUnlock()
if !ok {
return "", errors.New("prompter not found")
}
// Acquire the prompter.
prompter, ok := <-holder
if !ok {
return "", errors.New("unable to acquire prompter")
}
// Perform prompting.
response, err := prompter.Prompt(prompt)
// Return the prompter to the holder.
holder <- prompter
// Handle errors.
if err != nil {
return "", errors.Wrap(err, "unable to prompt")
}
// Success.
return response, nil
}