diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 73ed811..1163a22 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -20,6 +20,9 @@ type Flags struct { InstallerVersion string Lang, Version, Role, Mode, Runtime string + // LangAsked : the language was asked in this run (not given with --lang), so its step stays + // at the left once answered + LangAsked bool InstallRuntime bool Dir, Name string Port int diff --git a/internal/cli/install.go b/internal/cli/install.go index b02012e..af8478b 100644 --- a/internal/cli/install.go +++ b/internal/cli/install.go @@ -28,6 +28,8 @@ type wstep struct { label string // skip : not asked (a flag answered it, or the role or the mode has no such step) skip func() bool + // shown : at the left even when skipped (the language, asked before the wizard began) + shown func() bool // ask asks and keeps the answer ; ui.ErrBack goes to the step before ask func() error } @@ -43,21 +45,26 @@ type wstep struct { // // ErrAborted when stopped, or a step's own error. func runWizard(list []wstep) error { + // back : the step is reached with Esc, so one answered before the wizard began (shown at + // the left though skipped, the language) is asked again + back := false for i := 0; i < len(list); { ui.Steps(visibleLabels(list)...) s := list[i] - if s.skip != nil && s.skip() { + if s.skip != nil && s.skip() && !(back && s.shown != nil && s.shown()) { i++ continue } + back = false ui.Section(stepName(s.label)) err := s.ask() if errors.Is(err, ui.ErrBack) { j := i - 1 - for j > 0 && list[j].skip != nil && list[j].skip() { + for j > 0 && list[j].skip != nil && list[j].skip() && (list[j].shown == nil || !list[j].shown()) { j-- } i = max(0, j) + back = true continue } if err != nil { @@ -69,7 +76,8 @@ func runWizard(list []wstep) error { return nil } -// visibleLabels are the steps at the left : those not skipped, each once, in order. +// visibleLabels are the steps at the left : those not skipped, or shown anyway, each once, in +// order. // // Args: // @@ -82,7 +90,8 @@ func visibleLabels(list []wstep) []string { var out []string seen := map[string]bool{} for _, s := range list { - if (s.skip != nil && s.skip()) || seen[s.label] { + hidden := s.skip != nil && s.skip() && (s.shown == nil || !s.shown()) + if hidden || seen[s.label] { continue } seen[s.label] = true @@ -138,6 +147,7 @@ func install(ctx context.Context, f Flags) error { return err } f.Lang = i18n.Current() + f.LangAsked = true } if st, err := state.Load(); err == nil { ui.Warn(i18n.T("install.already", "version", st.Version, "mode", string(st.Mode))) @@ -182,7 +192,7 @@ func install(ctx context.Context, f Flags) error { var list []wstep list = []wstep{ // first the language, the server's locale chosen ; its steps renamed once changed - {label: "language", skip: func() bool { return ui.Unattended || f.Lang != "" }, ask: func() error { + {label: "language", skip: func() bool { return ui.Unattended || f.Lang != "" }, shown: func() bool { return f.LangAsked }, ask: func() error { return chooseLanguage(func() { ui.Relabel(visibleLabels(list)...) }) }}, // then what this version brings : its breaking changes, removals and deprecations on top diff --git a/internal/cli/install_test.go b/internal/cli/install_test.go new file mode 100644 index 0000000..9b91d51 --- /dev/null +++ b/internal/cli/install_test.go @@ -0,0 +1,64 @@ +package cli + +import ( + "reflect" + "testing" + + "github.com/ansibleforms/installer/internal/ui" +) + +// TestVisibleLabelsKeepsAnsweredLanguage checks that the language, asked before the wizard +// began (so its own step is skipped), stays at the left, while a step skipped for another +// reason does not show. +func TestVisibleLabelsKeepsAnsweredLanguage(t *testing.T) { + asked := true + skipped := func() bool { return true } + list := []wstep{ + {label: "language", skip: skipped, shown: func() bool { return asked }}, + {label: "notes"}, + {label: "folder", skip: skipped}, + {label: "role"}, + } + want := []string{stepName("language"), stepName("notes"), stepName("role")} + if got := visibleLabels(list); !reflect.DeepEqual(got, want) { + t.Fatalf("visibleLabels = %v, want %v", got, want) + } + // given with --lang : never asked, so not at the left + asked = false + want = []string{stepName("notes"), stepName("role")} + if got := visibleLabels(list); !reflect.DeepEqual(got, want) { + t.Fatalf("visibleLabels with --lang = %v, want %v", got, want) + } +} + +// TestRunWizardBackToAnsweredLanguage checks that Esc on the step after the language, asked +// before the wizard began, asks the language again, then goes on. +func TestRunWizardBackToAnsweredLanguage(t *testing.T) { + var asked []string + notesSeen := 0 + list := []wstep{ + {label: "language", skip: func() bool { return true }, shown: func() bool { return true }, ask: func() error { + asked = append(asked, "language") + return nil + }}, + {label: "notes", ask: func() error { + asked = append(asked, "notes") + notesSeen++ + if notesSeen == 1 { + return ui.ErrBack + } + return nil + }}, + {label: "role", ask: func() error { + asked = append(asked, "role") + return nil + }}, + } + if err := runWizard(list); err != nil { + t.Fatal(err) + } + want := []string{"notes", "language", "notes", "role"} + if !reflect.DeepEqual(asked, want) { + t.Fatalf("asked %v, want %v", asked, want) + } +} diff --git a/internal/cli/upgrade.go b/internal/cli/upgrade.go index f195e20..08639fd 100644 --- a/internal/cli/upgrade.go +++ b/internal/cli/upgrade.go @@ -54,6 +54,8 @@ func upgrade(ctx context.Context, f Flags) error { if err := chooseLanguage(func() { ui.Relabel(langLabels()...) }); err != nil { return err } + f.Lang = i18n.Current() + f.LangAsked = true } if st == nil { if st, err = offerFound(ctx, f, found, false); err != nil { @@ -86,25 +88,41 @@ func upgrade(ctx context.Context, f Flags) error { labels := func() []string { l := []string{stepName("version"), stepName("notes"), stepName("upgrade")} - if askLang { + // asked here, or by the install before it came to this upgrade + if f.LangAsked { l = append([]string{stepName("language")}, l...) } return l } ui.Steps(labels()...) - ui.Section(stepName("version")) - ui.Answered(from.String() + " → " + to.String()) - ui.Section(stepName("notes")) entries, err := releases.FetchChangelog(ctx, to) if err != nil { ui.Warn(err.Error()) } skipped := releases.Between(entries, from, to) - if len(skipped) > 0 { + for { + ui.Section(stepName("version")) + ui.Answered(from.String() + " → " + to.String()) + ui.Section(stepName("notes")) + if len(skipped) == 0 { + break + } ui.Explain(i18n.T("upgrade.notesHelp", "from", from.String(), "to", to.String())) - if err := ui.Document(i18n.T("upgrade.notesTitle", "count", len(skipped)), func() { showNotes(skipped) }); err != nil && !errors.Is(err, ui.ErrBack) { + err := ui.Document(i18n.T("upgrade.notesTitle", "count", len(skipped)), func() { showNotes(skipped) }) + if errors.Is(err, ui.ErrBack) && f.LangAsked { + // Esc : back to the language, asked in this run, then the notes again in it + ui.Section(stepName("language")) + if err := chooseLanguage(func() { ui.Relabel(labels()...) }); err != nil && !errors.Is(err, ui.ErrBack) { + return err + } + f.Lang = i18n.Current() + ui.Steps(labels()...) + continue + } + if err != nil && !errors.Is(err, ui.ErrBack) { return err } + break } // a Runner of 7.2 in database mode : its runner, its app and its mode asked and reviewed, // then moved to the installer's Runner layout (migrate.go)