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  }