Skip to content

Commit

Permalink
Add missing conflict confirmation
Browse files Browse the repository at this point in the history
  • Loading branch information
majori committed Dec 18, 2023
1 parent 407e5eb commit 180b673
Show file tree
Hide file tree
Showing 17 changed files with 172 additions and 68 deletions.
2 changes: 1 addition & 1 deletion internal/cli/cmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ func expectGivenError(ctx context.Context, expectedError string) error {
cmdStdErr := ctx.Value(cmdStdErrCtxKey{}).(*bytes.Buffer)

if matched, err := regexp.MatchString(expectedError, cmdStdErr.String()); !matched {
return fmt.Errorf("command produced unexpected error: Expected: '%s', Actual: '%s'", expectedError, cmdStdErr)
return fmt.Errorf("command produced unexpected error: Expected: '%s', Actual: '%s'", expectedError, strings.TrimSpace(cmdStdErr.String()))
} else if err != nil {
return fmt.Errorf("regexp pattern matching caused an error: %w", err)
}
Expand Down
18 changes: 4 additions & 14 deletions internal/cli/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import (
"github.com/futurice/jalapeno/pkg/engine"
"github.com/futurice/jalapeno/pkg/recipe"
"github.com/futurice/jalapeno/pkg/recipeutil"
"github.com/futurice/jalapeno/pkg/survey"
"github.com/futurice/jalapeno/pkg/ui/survey"
uiutil "github.com/futurice/jalapeno/pkg/ui/util"
"github.com/gofrs/uuid"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -129,23 +130,12 @@ func runExecute(cmd *cobra.Command, opts executeOptions) error {
varsWithoutValues := recipeutil.FilterVariablesWithoutValues(re.Variables, values)
if len(varsWithoutValues) > 0 {
if opts.NoInput {
var errMsg string
if len(varsWithoutValues) == 1 {
errMsg = fmt.Sprintf("value for variable %s is", varsWithoutValues[0].Name)
} else {
vars := make([]string, len(varsWithoutValues))
for i, v := range varsWithoutValues {
vars[i] = v.Name
}
errMsg = fmt.Sprintf("values for variables [%s] are", strings.Join(vars, ","))
}

return fmt.Errorf("%s missing and `--no-input` flag was set to true", errMsg)
return recipeutil.NewNoInputError(varsWithoutValues)
}

promptedValues, err := survey.PromptUserForValues(cmd.InOrStdin(), cmd.OutOrStdout(), varsWithoutValues, values)
if err != nil {
if errors.Is(err, survey.ErrUserAborted) {
if errors.Is(err, uiutil.ErrUserAborted) {
return nil
} else {
return fmt.Errorf("error when prompting for values: %s", err)
Expand Down
38 changes: 20 additions & 18 deletions internal/cli/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import (
"github.com/futurice/jalapeno/pkg/engine"
"github.com/futurice/jalapeno/pkg/recipe"
"github.com/futurice/jalapeno/pkg/recipeutil"
"github.com/futurice/jalapeno/pkg/survey"
"github.com/futurice/jalapeno/pkg/ui/conflict"
"github.com/futurice/jalapeno/pkg/ui/survey"
uiutil "github.com/futurice/jalapeno/pkg/ui/util"
"github.com/spf13/cobra"
"golang.org/x/mod/semver"
)
Expand Down Expand Up @@ -165,23 +167,12 @@ func runUpgrade(cmd *cobra.Command, opts upgradeOptions) error {

if len(varsWithoutValues) > 0 {
if opts.NoInput {
var errMsg string
if len(varsWithoutValues) == 1 {
errMsg = fmt.Sprintf("value for variable %s is", varsWithoutValues[0].Name)
} else {
vars := make([]string, len(varsWithoutValues))
for i, v := range varsWithoutValues {
vars[i] = v.Name
}
errMsg = fmt.Sprintf("values for variables [%s] are", strings.Join(vars, ","))
}

return fmt.Errorf("%s missing and `--no-input` flag was set to true", errMsg)
return recipeutil.NewNoInputError(varsWithoutValues)
}

promptedValues, err := survey.PromptUserForValues(cmd.InOrStdin(), cmd.OutOrStdout(), varsWithoutValues, predefinedValues)
if err != nil {
if errors.Is(err, survey.ErrUserAborted) {
if errors.Is(err, uiutil.ErrUserAborted) {
return nil
}

Expand Down Expand Up @@ -226,28 +217,37 @@ func runUpgrade(cmd *cobra.Command, opts upgradeOptions) error {
}

if prevFile, exists := oldSauce.Files[path]; exists && prevFile.HasBeenModified() {
if opts.NoInput {
return recipeutil.NewNoInputError(varsWithoutValues)
}

// The file contents has been modified
if !overrideNoticed {
cmd.Println("Some of the files has been manually modified. Do you want to override the following files:")
cmd.Println("\nSome of the files has been manually modified. Do you want to override the following files:")
overrideNoticed = true
}

// TODO: Ask the user should we override the file or not
// TODO: We could do better in terms of merge conflict management. Like show the diff or something
var override bool
override, err := conflict.Solve(cmd.InOrStdin(), cmd.OutOrStdout(), path)
if err != nil {
if errors.Is(err, uiutil.ErrUserAborted) {
cmd.Println("User aborted")
return nil
}

return fmt.Errorf("error when prompting for question: %w", err)
}

if !override {
// User decided not to override the file with manual changes, remove from
// list of changes to write
cmd.Printf("%s: keep\n", path)
continue
}
}

// Add new file or replace existing one
output[path] = newSauce.Files[path]
cmd.Printf("%s: replace\n", path)
}

newSauce.Files = output
Expand All @@ -257,5 +257,7 @@ func runUpgrade(cmd *cobra.Command, opts upgradeOptions) error {
return err
}

cmd.Printf("\nRecipe upgraded %s\n", opts.Colors.Green.Render("successfully!"))

return nil
}
1 change: 1 addition & 0 deletions internal/cli/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func iRunUpgrade(ctx context.Context, recipe string) (context.Context, error) {
args := []string{
"upgrade",
url,
"--no-input",
fmt.Sprintf("--dir=%s", projectDir),
}

Expand Down
17 changes: 17 additions & 0 deletions pkg/recipeutil/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,20 @@ func RowsToTable(columns []string, rows [][]string) ([]map[string]string, error)

return table, nil
}

func NewNoInputError(vars []recipe.Variable) error {
var errMsg string
switch len(vars) {
case 0:
return errors.New("there was file conflicts which needs to be manually resolved while `--no-input` flag was set to true")
case 1:
return fmt.Errorf("value for variable %s is missing and `--no-input` flag was set to true", vars[0].Name)
default:
varNames := make([]string, len(vars))
for i, v := range vars {
varNames[i] = v.Name
}
errMsg = fmt.Sprintf("values for variables [%s] are", strings.Join(varNames, ","))
return fmt.Errorf("%s missing and `--no-input` flag was set to true", errMsg)
}
}
94 changes: 94 additions & 0 deletions pkg/ui/conflict/conflict.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package conflict

import (
"errors"
"fmt"
"io"
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
uiutil "github.com/futurice/jalapeno/pkg/ui/util"
"github.com/muesli/termenv"
)

type Model struct {
Value bool
filePath string
err error
submitted bool
}

var _ tea.Model = Model{}

func Solve(in io.Reader, out io.Writer, filePath string) (bool, error) {
lipgloss.SetHasDarkBackground(termenv.HasDarkBackground())

p := tea.NewProgram(NewModel(filePath), tea.WithInput(in), tea.WithOutput(out))
if m, err := p.Run(); err != nil {
return false, err
} else {
m, ok := m.(Model)
if !ok {
return false, errors.New("internal error: unexpected model type")
}

if m.err != nil {
return false, m.err
}

return m.Value, nil
}
}

func NewModel(filePath string) Model {
return Model{
filePath: filePath,
}
}

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

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
m.err = uiutil.ErrUserAborted
return m, tea.Quit
case tea.KeyEnter:
m.submitted = true
return m, tea.Quit
case tea.KeyRight:
m.Value = true
case tea.KeyLeft:
m.Value = false
case tea.KeyRunes:
switch string(msg.Runes) {
case "y", "Y":
m.Value = true
case "n", "N":
m.Value = false
}
}
}
return m, nil
}

func (m Model) View() string {
if m.submitted || m.err != nil {
return ""
}

var s strings.Builder
s.WriteString(fmt.Sprintf("Override file '%s':\n", m.filePath))
if m.Value {
s.WriteString(fmt.Sprintf("> No/%s", lipgloss.NewStyle().Bold(true).Render("Yes")))
} else {
s.WriteString(fmt.Sprintf("> %s/Yes", lipgloss.NewStyle().Bold(true).Render("No")))
}

return s.String()
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import (

tea "github.com/charmbracelet/bubbletea"
"github.com/futurice/jalapeno/pkg/recipe"
"github.com/futurice/jalapeno/pkg/survey/util"
"github.com/futurice/jalapeno/pkg/ui/survey/style"
)

type ConfirmModel struct {
variable recipe.Variable
styles util.Styles
styles style.Styles
value bool
submitted bool
showDescription bool
}

var _ Model = ConfirmModel{}

func NewConfirmModel(v recipe.Variable, styles util.Styles) ConfirmModel {
func NewConfirmModel(v recipe.Variable, styles style.Styles) ConfirmModel {
return ConfirmModel{
variable: v,
styles: styles,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/futurice/jalapeno/pkg/recipe"
"github.com/futurice/jalapeno/pkg/survey/util"
"github.com/futurice/jalapeno/pkg/ui/survey/style"
)

const listHeight = 14
Expand All @@ -22,7 +22,7 @@ var (
type SelectModel struct {
variable recipe.Variable
list list.Model
styles util.Styles
styles style.Styles
value string
showDescription bool
submitted bool
Expand Down Expand Up @@ -59,7 +59,7 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
fmt.Fprint(w, fn(string(i)))
}

func NewSelectModel(v recipe.Variable, styles util.Styles) SelectModel {
func NewSelectModel(v recipe.Variable, styles style.Styles) SelectModel {
items := make([]list.Item, len(v.Options))
for i := range v.Options {
items[i] = item(v.Options[i])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/futurice/jalapeno/pkg/recipe"
"github.com/futurice/jalapeno/pkg/survey/util"
"github.com/futurice/jalapeno/pkg/ui/survey/style"
"github.com/futurice/jalapeno/pkg/ui/util"
)

type StringModel struct {
variable recipe.Variable
textInput textinput.Model
styles util.Styles
styles style.Styles
submitted bool
showDescription bool
err error
}

var _ Model = StringModel{}

func NewStringModel(v recipe.Variable, styles util.Styles) StringModel {
func NewStringModel(v recipe.Variable, styles style.Styles) StringModel {
ti := textinput.New()
ti.Focus()
ti.CharLimit = 156
Expand Down
8 changes: 4 additions & 4 deletions pkg/survey/prompt/table.go → pkg/ui/survey/prompt/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/futurice/jalapeno/pkg/recipe"
"github.com/futurice/jalapeno/pkg/recipeutil"
"github.com/futurice/jalapeno/pkg/survey/editable"
"github.com/futurice/jalapeno/pkg/survey/util"
"github.com/futurice/jalapeno/pkg/ui/editable"
"github.com/futurice/jalapeno/pkg/ui/survey/style"
)

type TableModel struct {
variable recipe.Variable
table editable.Model
styles util.Styles
styles style.Styles
submitted bool
showDescription bool

Expand All @@ -25,7 +25,7 @@ type TableModel struct {

var _ Model = TableModel{}

func NewTableModel(v recipe.Variable, styles util.Styles) TableModel {
func NewTableModel(v recipe.Variable, styles style.Styles) TableModel {
cols := make([]editable.Column, len(v.Columns))

validators := make(map[string][]func(string) error)
Expand Down
13 changes: 2 additions & 11 deletions pkg/survey/util/util.go → pkg/ui/survey/style/style.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
package util
package style

import (
"errors"

"github.com/charmbracelet/lipgloss"
)

var (
ErrRequired = errors.New("value can not be empty")
ErrRegExFailed = errors.New("validation failed")
)
import "github.com/charmbracelet/lipgloss"

type Styles struct {
VariableName lipgloss.Style
Expand Down
Loading

0 comments on commit 180b673

Please sign in to comment.