github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/prompting/registry.go (about) 1 package prompting 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 8 "github.com/mutagen-io/mutagen/pkg/identifier" 9 ) 10 11 // registryLock is the lock on the global prompter registry. 12 var registryLock sync.RWMutex 13 14 // registry is the global prompter registry. 15 var registry = make(map[string]chan Prompter) 16 17 // RegisterPrompter registers a prompter with the global registry. It 18 // automatically generates a unique identifier for the prompter. 19 func RegisterPrompter(prompter Prompter) (string, error) { 20 // Generate a unique identifier for this prompter. 21 identifier, err := identifier.New(identifier.PrefixPrompter) 22 if err != nil { 23 return "", fmt.Errorf("unable to generate prompter identifier: %w", err) 24 } 25 26 // Perform registration. 27 if err := RegisterPrompterWithIdentifier(identifier, prompter); err != nil { 28 return "", err 29 } 30 31 // Success. 32 return identifier, nil 33 } 34 35 // RegisterPrompterWithIdentifier registers a prompter with the global registry 36 // using the specified identifier. 37 func RegisterPrompterWithIdentifier(identifier string, prompter Prompter) error { 38 // Enforce that the identifier is non-empty. 39 if identifier == "" { 40 return errors.New("empty identifier") 41 } 42 43 // Create and populate a "holder" (channel) for passing the prompter around. 44 holder := make(chan Prompter, 1) 45 holder <- prompter 46 47 // Lock the registry for writing and defer its release. 48 registryLock.Lock() 49 defer registryLock.Unlock() 50 51 // Check for identifier collisions. This won't be a problem with our 52 // internally generated identifiers, but since this method accepts arbitrary 53 // identifiers, we want to be sure to avoid collisions. 54 if _, ok := registry[identifier]; ok { 55 return errors.New("identifier collision") 56 } 57 58 // Register the holder. 59 registry[identifier] = holder 60 61 // Success. 62 return nil 63 } 64 65 // UnregisterPrompter unregisters a prompter from the global registry. If the 66 // prompter is not registered, this method panics. If a prompter is unregistered 67 // with prompts pending for it, they will be cancelled. 68 func UnregisterPrompter(identifier string) { 69 // Lock the registry for writing, grab the holder, and remove it from the 70 // registry. If it isn't currently registered, this must be a logic error. 71 registryLock.Lock() 72 holder, ok := registry[identifier] 73 if !ok { 74 panic("deregistration requested for unregistered prompter") 75 } 76 delete(registry, identifier) 77 registryLock.Unlock() 78 79 // Get the prompter back and close the holder to let anyone else who has it 80 // know that they won't be getting the prompter from it. 81 <-holder 82 close(holder) 83 } 84 85 // Message invokes the Message method on a prompter in the global registry. If 86 // the prompter identifier provided is an empty string, this method is a no-op 87 // and returns a nil error. 88 func Message(identifier, message string) error { 89 // If the prompter identifier is empty, don't do anything. 90 if identifier == "" { 91 return nil 92 } 93 94 // Grab the holder for the specified prompter. We only need a read lock on 95 // the registry for this purpose. 96 registryLock.RLock() 97 holder, ok := registry[identifier] 98 registryLock.RUnlock() 99 if !ok { 100 return errors.New("prompter not found") 101 } 102 103 // Acquire the prompter. 104 prompter, ok := <-holder 105 if !ok { 106 return errors.New("unable to acquire prompter") 107 } 108 109 // Perform messaging. 110 err := prompter.Message(message) 111 112 // Return the prompter to the holder. 113 holder <- prompter 114 115 // Handle errors. 116 if err != nil { 117 return fmt.Errorf("unable to message: %w", err) 118 } 119 120 // Success. 121 return nil 122 } 123 124 // Prompt invokes the Prompt method on a prompter in the global registry. 125 func Prompt(identifier, prompt string) (string, error) { 126 // Grab the holder for the specified prompter. We only need a read lock on 127 // the registry for this purpose. 128 registryLock.RLock() 129 holder, ok := registry[identifier] 130 registryLock.RUnlock() 131 if !ok { 132 return "", errors.New("prompter not found") 133 } 134 135 // Acquire the prompter. 136 prompter, ok := <-holder 137 if !ok { 138 return "", errors.New("unable to acquire prompter") 139 } 140 141 // Perform prompting. 142 response, err := prompter.Prompt(prompt) 143 144 // Return the prompter to the holder. 145 holder <- prompter 146 147 // Handle errors. 148 if err != nil { 149 return "", fmt.Errorf("unable to prompt: %w", err) 150 } 151 152 // Success. 153 return response, nil 154 }