Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create prod-ready quickstart command #75

Merged
merged 6 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions cmd/quickstart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cmd

import (
"fmt"
"log"
"os"

tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"

"ldcli/internal/flags"
"ldcli/internal/quickstart"
)

func NewQuickStartCmd(client flags.Client) *cobra.Command {
return &cobra.Command{
Long: "",
RunE: runQuickStart(client),
Short: "Setup guide to create your first feature flag",
Use: "setup",
}
}

func runQuickStart(client flags.Client) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
f, err := tea.LogToFile("debug.log", "")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a ticket to set up configurable logging.

if err != nil {
fmt.Println("could not open file for debugging:", err)
os.Exit(1)
}
defer f.Close()

_, err = tea.NewProgram(quickstart.NewContainerModel(client)).Run()
if err != nil {
log.Fatal(err)
}

return nil
}
}
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
)

func NewRootCommand(flagsClient flags.Client, membersClient members.Client, projectsClient projects.Client) (*cobra.Command, error) {

cmd := &cobra.Command{
Use: "ldcli",
Short: "LaunchDarkly CLI",
Expand Down Expand Up @@ -72,6 +71,7 @@ func NewRootCommand(flagsClient flags.Client, membersClient members.Client, proj
return nil, err
}

cmd.AddCommand(NewQuickStartCmd(flagsClient))
cmd.AddCommand(flagsCmd)
cmd.AddCommand(membersCmd)
cmd.AddCommand(projectsCmd)
Expand Down
2 changes: 1 addition & 1 deletion cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

var setupCmd = &cobra.Command{
Use: "setup",
Use: "setup-TOREMOVE",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep this in so we can run the two setups in parallel for comparison until the new one is done.

Short: "Setup guide to create your first feature flag",
Long: "",
RunE: runSetup,
Expand Down
105 changes: 105 additions & 0 deletions internal/quickstart/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package quickstart

import (
"fmt"

"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"

"ldcli/internal/flags"
)

// step is an identifier for each step in the quick-start flow.
type step int

const (
createFlagStep step = iota
)

// ContainerModel is a high level container model that controls the nested models which each
// represent a step in the quick-start flow.
type ContainerModel struct {
currentStep step
err error
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll use these from the model that creates the flag so we can display them later.

flagKey string
flagsClient flags.Client
quitting bool
steps []tea.Model
}

func NewContainerModel(flagsClient flags.Client) tea.Model {
return ContainerModel{
currentStep: createFlagStep,
flagsClient: flagsClient,
steps: []tea.Model{},
}
}

func (m ContainerModel) Init() tea.Cmd {
return nil
}

func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Enter):
switch m.currentStep {
case createFlagStep:
// TODO: add createFlagModel
default:
}
case key.Matches(msg, keys.Quit):
m.quitting = true

return m, tea.Quit
default:
// delegate all other input to the current model

// TODO: update model once there is at least one
// updated, _ := m.steps[m.currentStep].Update(msg)
// m.steps[m.currentStep] = updated
}
default:
}

return m, nil
}

func (m ContainerModel) View() string {
if m.quitting {
return ""
}

if m.err != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was trivial enough to add. Another ticket will make it look nice.

return lipgloss.
NewStyle().
Foreground(lipgloss.Color("#eb4034")).
SetString(m.err.Error()).
Render()
}

// TODO: remove after creating more steps
if m.currentStep > createFlagStep {
return fmt.Sprintf("created flag %s", m.flagKey)
}

return fmt.Sprintf("\nStep %d of %d\n"+m.steps[m.currentStep].View(), m.currentStep+1, len(m.steps))
}

type keyMap struct {
Enter key.Binding
Quit key.Binding
}

var keys = keyMap{
Enter: key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "select"),
),
Quit: key.NewBinding(
key.WithKeys("ctrl+c"),
key.WithHelp("q", "quit"),
),
}
Loading