Selaa lähdekoodia

Connected UI create widget to IO funcs

arianagiroux 2 viikkoa sitten
vanhempi
sitoutus
3bbb7ac0ab
3 muutettua tiedostoa jossa 148 lisäystä ja 9 poistoa
  1. 4 2
      Readme.md
  2. 88 7
      tui.go
  3. 56 0
      tui_test.go

+ 4 - 2
Readme.md

@@ -18,9 +18,11 @@ go install cmd/issues.go
 - `cmd/issues.go:// TODO implement attempt to auto load issues folder if no arg specified`
 - `io.go:func DeleteIssue(issue Issue) (success bool, err error) { return false, nil } // TODO: implement`
 - `tui.go:// TODO enable collection recursing (i.e, embeded collections)`
-- `tui.go:// TODO enable scroll/viewport logic on issues!!!`
-- `tui.go:// TODO connect WriteIssue to createIssue widget`
+- `tui.go:// TODO enable scroll/viewport logic`
 - `tui.go:	case IssueCollection: // TODO handle updates to IssueCollection widgets in its own update func`
+- `tui.go:// TODO add keyhelp func to widget interface`
+- `tui.go:// TODO invoke editor for descriptions`
+- `tui.go:// TODO implement description field in createIssue.create cmd`
 
 ## See also
 

+ 88 - 7
tui.go

@@ -2,9 +2,7 @@
 //
 // TODO enable collection recursing (i.e, embeded collections)
 //
-// TODO enable scroll/viewport logic on issues!!!
-
-// TODO connect WriteIssue to createIssue widget
+// TODO enable scroll/viewport logic
 //
 // While the package remains in v0.0.X releases, this TUI may be undocumented.
 package issues
@@ -154,6 +152,8 @@ func (m Model) View() string {
 // ----------------------------------------------------------------------------
 
 // interface definition for widgets
+//
+// TODO add keyhelp func to widget interface
 type widget interface {
 	render() tea.Msg
 }
@@ -168,14 +168,16 @@ type inputField struct {
 }
 
 // widget for creating an issue
+//
+// TODO invoke editor for descriptions
 type createIssue struct {
 	inputFields []inputField
 	Path        string
 	selected    int
-	Err         error // behaviour undefined
+	Err         error // not implemented
 }
 
-func initialInputModel(path string, placeholder string) createIssue {
+func initialCreateIssueModel(path string, placeholder string) createIssue {
 	spawnInput := func(f bool) textinput.Model {
 		ti := textinput.New()
 		ti.Placeholder = placeholder
@@ -258,13 +260,25 @@ func (ci createIssue) update(msg tea.Msg) (createIssue, tea.Cmd) {
 			if ci.selected == len(ci.inputFields) { // confirm create
 				ci.selected++
 			} else if ci.selected == len(ci.inputFields)+1 { // confirmed
-				cmds = append(cmds, tea.Quit)
+				cmds = append(cmds, ci.create)
 			} else {
 				incrementSelected()
 			}
 		case "esc": // cancel
 			cmds = append(cmds, tea.Quit)
 		}
+	case createResult:
+		cmds = append(cmds, ci.write(Issue(msg)))
+	case writeResult:
+		switch value := msg.(type) {
+		case bool:
+			if !value {
+			} else {
+				cmds = append(cmds, tea.Quit)
+			}
+		case error:
+			panic(value)
+		}
 	}
 
 	for i, ti := range ci.inputFields {
@@ -403,5 +417,72 @@ func (m Model) load() tea.Msg {
 		return collection
 	}
 
-	return initialInputModel(m.Path, "lorem ipsum")
+	return initialCreateIssueModel(m.Path, "lorem ipsum")
+}
+
+type createResult Issue
+
+// A widget for creating issues
+//
+// TODO implement description field in createIssue.create cmd
+func (ci createIssue) create() tea.Msg {
+	data := make(map[string]string)
+	commaSplit := func(t string) []string {
+		s := strings.Split(t, ",")
+		for i, v := range s {
+			s[i] = strings.TrimLeft(v, " \t\n")
+			s[i] = strings.TrimRight(s[i], " \t\n")
+
+			s[i] = parseHumanToPath(s[i])
+		}
+		return s
+	}
+
+	for _, field := range ci.inputFields {
+		data[field.title] = field.input.Value()
+	}
+
+	var newIssue = Issue{
+		Path:      ci.Path,
+		Tags:      VariadicField{Path: "/tags"},
+		Blockedby: VariadicField{Path: "/blockedby"},
+	}
+
+	for key, value := range data {
+		switch key {
+		case "title":
+			newIssue.Title = value
+		case "status":
+			newIssue.Status = Field{Path: "/status", Data: value}
+		case "description":
+			newIssue.Description = Field{Path: "/description", Data: value}
+		case "tags":
+			splitTags := commaSplit(value)
+			for _, tag := range splitTags {
+				newIssue.Tags.Fields = append(newIssue.Tags.Fields, Field{Path: tag})
+			}
+		case "blockers":
+			splitBlockedby := commaSplit(value)
+			for _, blocker := range splitBlockedby {
+				newIssue.Blockedby.Fields = append(
+					newIssue.Blockedby.Fields, Field{Path: blocker},
+				)
+			}
+		}
+	}
+
+	return createResult(newIssue)
+}
+
+type writeResult any
+
+// Wraps a cmd func, passes an initialized Issue to WriteIssue()
+func (ci createIssue) write(issue Issue) tea.Cmd {
+	return func() tea.Msg {
+		result, err := WriteIssue(issue, false)
+		if err != nil {
+			return writeResult(err)
+		}
+		return writeResult(result)
+	}
 }

+ 56 - 0
tui_test.go

@@ -1,6 +1,7 @@
 package issues
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
@@ -196,3 +197,58 @@ func Test_Model_View(t *testing.T) {
 	render2 := Model{}.View()
 	assert.Equal(t, "loading...", render2)
 }
+
+func Test_createIssue_create_cmd(t *testing.T) {
+	// test data init
+	testData := make(map[string]string)
+
+	// because the resulting test data never gets written from memory to disk,
+	// we can set just about any test path
+	testCi := initialCreateIssueModel("tests/bugs/test-create-in-memory", "lorem ipsum")
+	for i := range testCi.inputFields {
+		testData[testCi.inputFields[i].title] = fmt.Sprintf("test%d", i)
+		testCi.inputFields[i].input.SetValue(fmt.Sprintf("test%d", i))
+	}
+
+	commaSplit := func(t string) []string {
+		s := strings.Split(t, ",")
+		for i, v := range s {
+			s[i] = strings.TrimLeft(v, " \t\n")
+			s[i] = strings.TrimRight(s[i], " \t\n")
+
+			s[i] = parseHumanToPath(s[i])
+		}
+		return s
+	}
+
+	var testIssue = Issue{
+		Path:      "tests/bugs/test-create-in-memory",
+		Tags:      VariadicField{Path: "/tags"},
+		Blockedby: VariadicField{Path: "/blockedby"},
+	}
+
+	for key, value := range testData {
+		switch key {
+		case "title":
+			testIssue.Title = value
+		case "status":
+			testIssue.Status = Field{Path: "/status", Data: value}
+		case "description":
+			testIssue.Description = Field{Path: "/description", Data: value}
+		case "tags":
+			splitTags := commaSplit(value)
+			for _, tag := range splitTags {
+				testIssue.Tags.Fields = append(testIssue.Tags.Fields, Field{Path: tag})
+			}
+		case "blockers":
+			splitBlockedby := commaSplit(value)
+			for _, blocker := range splitBlockedby {
+				testIssue.Blockedby.Fields = append(testIssue.Blockedby.Fields, Field{Path: blocker})
+			}
+		}
+	}
+
+	result := testCi.create()
+	assert.IsType(t, createResult(testIssue), result)
+	assert.Equal(t, createResult(testIssue).Path, result.(createResult).Path)
+}