implement label op+command

Michael Muré created

Change summary

bug/label.go                     |   4 +
bug/operation.go                 |   1 
bug/operations/label_change.go   |  56 +++++++++++++++++
bug/operations/operations.go     |   1 
commands/command.go              |   1 
commands/label.go                | 108 ++++++++++++++++++++++++++++++++++
commands/show.go                 |  11 ++
tests/operation_iterator_test.go |   5 +
8 files changed, 184 insertions(+), 3 deletions(-)

Detailed changes

bug/label.go 🔗

@@ -1,3 +1,7 @@
 package bug
 
 type Label string
+
+func (l Label) String() string {
+	return string(l)
+}

bug/operation.go 🔗

@@ -10,6 +10,7 @@ const (
 	SetTitleOp
 	AddCommentOp
 	SetStatusOp
+	LabelChangeOp
 )
 
 type Operation interface {

bug/operations/label_change.go 🔗

@@ -0,0 +1,56 @@
+package operations
+
+import (
+	"github.com/MichaelMure/git-bug/bug"
+	"sort"
+)
+
+// LabelChangeOperation will add or remove a set of labels
+
+var _ bug.Operation = LabelChangeOperation{}
+
+type LabelChangeOperation struct {
+	bug.OpBase
+	Added   []bug.Label
+	Removed []bug.Label
+}
+
+func NewLabelChangeOperation(author bug.Person, added, removed []bug.Label) LabelChangeOperation {
+	return LabelChangeOperation{
+		OpBase:  bug.NewOpBase(bug.LabelChangeOp, author),
+		Added:   added,
+		Removed: removed,
+	}
+}
+
+func (op LabelChangeOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
+	// Add in the set
+AddLoop:
+	for _, added := range op.Added {
+		for _, label := range snapshot.Labels {
+			if label == added {
+				// Already exist
+				continue AddLoop
+			}
+		}
+
+		snapshot.Labels = append(snapshot.Labels, added)
+	}
+
+	// Remove in the set
+	for _, removed := range op.Removed {
+		for i, label := range snapshot.Labels {
+			if label == removed {
+				snapshot.Labels[i] = snapshot.Labels[len(snapshot.Labels)-1]
+				snapshot.Labels = snapshot.Labels[:len(snapshot.Labels)-1]
+			}
+		}
+	}
+
+	// Sort
+	sort.Slice(snapshot.Labels, func(i, j int) bool {
+		return string(snapshot.Labels[i]) < string(snapshot.Labels[j])
+	})
+
+	return snapshot
+}

bug/operations/operations.go 🔗

@@ -8,4 +8,5 @@ func init() {
 	gob.Register(CreateOperation{})
 	gob.Register(SetTitleOperation{})
 	gob.Register(SetStatusOperation{})
+	gob.Register(LabelChangeOperation{})
 }

commands/command.go 🔗

@@ -47,6 +47,7 @@ func init() {
 		"close":    closeCmd,
 		"commands": commandsCmd,
 		"comment":  commentCmd,
+		"label":    labelCmd,
 		"ls":       lsCmd,
 		"new":      newCmd,
 		"open":     openCmd,

commands/label.go 🔗

@@ -0,0 +1,108 @@
+package commands
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/bug/operations"
+	"github.com/MichaelMure/git-bug/repository"
+)
+
+var labelFlagSet = flag.NewFlagSet("label", flag.ExitOnError)
+
+var (
+	labelRemove = newFlagSet.Bool("r", false, "Remove a label")
+)
+
+func runLabel(repo repository.Repo, args []string) error {
+	labelFlagSet.Parse(args)
+	args = labelFlagSet.Args()
+
+	if len(args) == 0 {
+		return errors.New("You must provide a bug id")
+	}
+
+	if len(args) == 1 {
+		return errors.New("You must provide a label")
+	}
+
+	prefix := args[0]
+
+	b, err := bug.FindBug(repo, prefix)
+	if err != nil {
+		return err
+	}
+
+	author, err := bug.GetUser(repo)
+	if err != nil {
+		return err
+	}
+
+	var added, removed []bug.Label
+
+	snap := b.Compile()
+
+	for _, arg := range args[1:] {
+		label := bug.Label(arg)
+
+		if *labelRemove {
+			// check for duplicate
+			if labelExist(removed, label) {
+				fmt.Printf("label \"%s\" is a duplicate\n", arg)
+				continue
+			}
+
+			// check that the label actually exist
+			if !labelExist(snap.Labels, label) {
+				fmt.Printf("label \"%s\" doesn't exist on this bug\n", arg)
+				continue
+			}
+
+			removed = append(removed, label)
+		} else {
+			// check for duplicate
+			if labelExist(added, label) {
+				fmt.Printf("label \"%s\" is a duplicate\n", arg)
+				continue
+			}
+
+			// check that the label doesn't already exist
+			if labelExist(snap.Labels, label) {
+				fmt.Printf("label \"%s\" is already set on this bug\n", arg)
+				continue
+			}
+
+			added = append(added, label)
+		}
+	}
+
+	if len(added) == 0 && len(removed) == 0 {
+		return errors.New("no label added or removed")
+	}
+
+	labelOp := operations.NewLabelChangeOperation(author, added, removed)
+
+	b.Append(labelOp)
+
+	err = b.Commit(repo)
+
+	return err
+}
+
+func labelExist(labels []bug.Label, label bug.Label) bool {
+	for _, l := range labels {
+		if l == label {
+			return true
+		}
+	}
+
+	return false
+}
+
+var labelCmd = &Command{
+	Description: "Manipulate bug's label",
+	Usage:       "<id> [<option>...] [<label>...]",
+	flagSet:     labelFlagSet,
+	RunMethod:   runLabel,
+}

commands/show.go 🔗

@@ -9,8 +9,6 @@ import (
 	"strings"
 )
 
-var line = strings.Repeat("-", 50)
-
 func runShowBug(repo repository.Repo, args []string) error {
 	if len(args) > 1 {
 		return errors.New("Only showing one bug at a time is supported")
@@ -47,6 +45,15 @@ func runShowBug(repo repository.Repo, args []string) error {
 		firstComment.FormatTime(),
 	)
 
+	var labels = make([]string, len(snapshot.Labels))
+	for i := range snapshot.Labels {
+		labels[i] = string(snapshot.Labels[i])
+	}
+
+	fmt.Printf("labels: %s\n\n",
+		strings.Join(labels, ", "),
+	)
+
 	// Comments
 	indent := "  "
 

tests/operation_iterator_test.go 🔗

@@ -17,6 +17,7 @@ var (
 	setTitleOp    = operations.NewSetTitleOp(rene, "title2")
 	addCommentOp  = operations.NewAddCommentOp(rene, "message2")
 	setStatusOp   = operations.NewSetStatusOp(rene, bug.ClosedStatus)
+	labelChangeOp = operations.NewLabelChangeOperation(rene, []bug.Label{"added"}, []bug.Label{"removed"})
 	mockRepo      = repository.NewMockRepoForTest()
 )
 
@@ -30,7 +31,9 @@ func TestOpIterator(t *testing.T) {
 
 	bug1.Append(createOp)
 	bug1.Append(setTitleOp)
+	bug1.Append(addCommentOp)
 	bug1.Append(setStatusOp)
+	bug1.Append(labelChangeOp)
 	bug1.Commit(mockRepo)
 
 	bug1.Append(setTitleOp)
@@ -50,7 +53,7 @@ func TestOpIterator(t *testing.T) {
 		counter++
 	}
 
-	if counter != 9 {
+	if counter != 11 {
 		t.Fatalf("Wrong count of value iterated (%d instead of 8)", counter)
 	}
 }