aboutsummaryrefslogtreecommitdiffstats
path: root/commands/select/select.go
diff options
context:
space:
mode:
Diffstat (limited to 'commands/select/select.go')
-rw-r--r--commands/select/select.go156
1 files changed, 156 insertions, 0 deletions
diff --git a/commands/select/select.go b/commands/select/select.go
new file mode 100644
index 00000000..b821ba59
--- /dev/null
+++ b/commands/select/select.go
@@ -0,0 +1,156 @@
+package _select
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/pkg/errors"
+
+ "github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/entity"
+)
+
+type ErrNoValidId struct {
+ typename string
+}
+
+func NewErrNoValidId(typename string) *ErrNoValidId {
+ return &ErrNoValidId{typename: typename}
+}
+
+func (e ErrNoValidId) Error() string {
+ return fmt.Sprintf("you must provide a %s id or use the \"select\" command first", e.typename)
+}
+
+func IsErrNoValidId(err error) bool {
+ _, ok := err.(*ErrNoValidId)
+ return ok
+}
+
+type Resolver[CacheT cache.CacheEntity] interface {
+ Resolve(id entity.Id) (CacheT, error)
+ ResolvePrefix(prefix string) (CacheT, error)
+}
+
+// Resolve first try to resolve an entity using the first argument of the command
+// line. If it fails, it falls back to the select mechanism.
+//
+// Returns:
+// - the entity if any
+// - the new list of command line arguments with the entity prefix removed if it
+// has been used
+// - an error if the process failed
+func Resolve[CacheT cache.CacheEntity](repo *cache.RepoCache,
+ typename string, namespace string, resolver Resolver[CacheT],
+ args []string) (CacheT, []string, error) {
+ // At first, try to use the first argument as an entity prefix
+ if len(args) > 0 {
+ cached, err := resolver.ResolvePrefix(args[0])
+
+ if err == nil {
+ return cached, args[1:], nil
+ }
+
+ if !entity.IsErrNotFound(err) {
+ return *new(CacheT), nil, err
+ }
+ }
+
+ // first arg is not a valid entity prefix, we can safely use the preselected entity if any
+
+ cached, err := selected(repo, resolver, namespace)
+
+ // selected entity is invalid
+ if entity.IsErrNotFound(err) {
+ // we clear the selected bug
+ err = Clear(repo, namespace)
+ if err != nil {
+ return *new(CacheT), nil, err
+ }
+ return *new(CacheT), nil, NewErrNoValidId(typename)
+ }
+
+ // another error when reading the entity
+ if err != nil {
+ return *new(CacheT), nil, err
+ }
+
+ // entity is successfully retrieved
+ if cached != nil {
+ return *cached, args, nil
+ }
+
+ // no selected bug and no valid first argument
+ return *new(CacheT), nil, NewErrNoValidId(typename)
+}
+
+func selectFileName(namespace string) string {
+ return filepath.Join("select", namespace)
+}
+
+// Select will select a bug for future use
+func Select(repo *cache.RepoCache, namespace string, id entity.Id) error {
+ filename := selectFileName(namespace)
+ f, err := repo.LocalStorage().OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ return err
+ }
+
+ _, err = f.Write([]byte(id.String()))
+ if err != nil {
+ return err
+ }
+
+ return f.Close()
+}
+
+// Clear will clear the selected entity, if any
+func Clear(repo *cache.RepoCache, namespace string) error {
+ filename := selectFileName(namespace)
+ return repo.LocalStorage().Remove(filename)
+}
+
+func selected[CacheT cache.CacheEntity](repo *cache.RepoCache, resolver Resolver[CacheT], namespace string) (*CacheT, error) {
+ filename := selectFileName(namespace)
+ f, err := repo.LocalStorage().Open(filename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ } else {
+ return nil, err
+ }
+ }
+
+ buf, err := ioutil.ReadAll(io.LimitReader(f, 100))
+ if err != nil {
+ return nil, err
+ }
+ if len(buf) == 100 {
+ return nil, fmt.Errorf("the select file should be < 100 bytes")
+ }
+
+ id := entity.Id(buf)
+ if err := id.Validate(); err != nil {
+ err = repo.LocalStorage().Remove(filename)
+ if err != nil {
+ return nil, errors.Wrap(err, "error while removing invalid select file")
+ }
+
+ return nil, fmt.Errorf("select file in invalid, removing it")
+ }
+
+ cached, err := resolver.Resolve(id)
+ if err != nil {
+ return nil, err
+ }
+
+ err = f.Close()
+ if err != nil {
+ return nil, err
+ }
+
+ return &cached, nil
+}