aboutsummaryrefslogblamecommitdiffstats
path: root/commands/select/select.go
blob: cd12250efce78fa89ef0079947df78816e083af2 (plain) (tree)
1
2
3
4
5




               


































                                                                                                      



                                                                                  

























































                                                                                                  
                             






















                                                                                                                              
                                                      
                       
                             

                               






                               

















                                                                                                

                           
package _select

import (
	"fmt"
	"io"
	"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 {
		_ = f.Close()
		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 := io.ReadAll(io.LimitReader(f, 100))
	if err != nil {
		_ = f.Close()
		return nil, err
	}

	err = f.Close()
	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
	}

	return &cached, nil
}