1 Commits
Author SHA1 Message Date
Michael WesemannandClaude Opus 5 c22a66c470 [mike@mwxm4]
The customisation picker opens on the site's own road.

A domain, a netmask and a gateway in ~/.gvmrc are written down so that machines
get them — that is what writing them down was for — so the line that gives them
to the guest is where the cursor starts, rather than the one that leaves the
guest as the template made it:

      leave the guest as the template made it
    ▸ this site — fhi-berlin.mpg.de, gateway 10.0.0.1
      linux-static
      windows-domain

Where no site is set up it starts at the top as before, on the answer that
cannot be wrong. Nothing is decided by where the cursor sits: the picker picks
on Enter, the address is asked for after it, and the confirmation still says in
full what will happen before anything is made.

What the picker offers is a function of its own now (customChoices), which is
what made it testable: the lines, what each of them means, and where to open
are one answer to one question and were three statements spread through a
method that also asks four others.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:57:33 +02:00
4 changed files with 101 additions and 22 deletions
+13 -6
View File
@@ -545,16 +545,23 @@ are open —
customise web05 how v308
▸ leave the guest as the template made it
this site — fhi-berlin.mpg.de, gateway 10.0.0.1
leave the guest as the template made it
▸ this site — fhi-berlin.mpg.de, gateway 10.0.0.1
linux-static
windows-domain
— and then one line for the address, which says in words what leaving it empty
would do:
The cursor starts on gvm's own road wherever the site is set up: a domain, a
netmask and a gateway written into `~/.gvmrc` are written down so that machines
get them, and the answer wanted nearly every time should not be the one that has
to be arrowed to. Where the site is not set up it starts at the top instead, on
the answer that is never wrong. Nothing is decided by where it sits — the picker
picks on Enter, and the confirmation still says in full what will happen.
address for web05, or empty for DHCP:
address for web05, or empty to leave it to linux-static:
Then one line for the address, which says in words what leaving it empty would
do:
address for web05, empty for DHCP:
address for web05, empty to leave it to linux-static:
Where that picker does **not** appear, the confirmation says why rather than
leaving a step to look broken — neither road is open, because the vCenter holds
+50 -15
View File
@@ -372,7 +372,19 @@ func (b *browser) openPicker(r vmRow, kind pickKind) {
// callback runs after the picker has closed, so that what it does — a
// confirmation, another question — has the screen to itself.
func (b *browser) choose(title string, r vmRow, lines []string, chosen func(int)) {
b.pick = &picker{title: title, row: r, lines: lines, chosen: chosen}
b.chooseAt(title, r, lines, 0, chosen)
}
// chooseAt is the same with the cursor starting somewhere other than the top,
// for a list whose ordinary answer is not its first line. Where the cursor
// starts is the only default a picker has — nothing is picked until Enter — so
// it belongs on the line somebody nearly always wants, and the others are one
// key away.
func (b *browser) chooseAt(title string, r vmRow, lines []string, at int, chosen func(int)) {
if at < 0 || at >= len(lines) {
at = 0
}
b.pick = &picker{title: title, row: r, lines: lines, chosen: chosen, sel: at}
}
func (b *browser) closePicker() { b.pick = nil }
@@ -677,21 +689,9 @@ func (b *browser) deploy(r vmRow) {
return
}
lines := []string{"leave the guest as the template made it"}
kinds := []string{""} // what each line means: "" none, "-" gvm's own, else a name
if own {
// What it will do, not where the settings are kept: a picker line that
// is mostly an absolute path says nothing about the choice being made,
// and `gvm config` is where the file is named.
lines = append(lines, SF("this site — %s, gateway %s", b.site.domain, b.site.gateway))
kinds = append(kinds, "-")
}
for _, name := range specs {
lines = append(lines, name)
kinds = append(kinds, name)
}
lines, kinds, at := customChoices(b.site, specs, own)
b.choose(SF("customise %s how", name), r, lines, func(i int) {
b.chooseAt(SF("customise %s how", name), r, lines, at, func(i int) {
switch kinds[i] {
case "":
b.deployAsk(r, src, target, name, deployOpts{}, deployStep{})
@@ -705,6 +705,41 @@ func (b *browser) deploy(r vmRow) {
})
}
// customChoices is what the customisation picker offers, what each line means,
// and where its cursor starts.
//
// kinds is the meaning, carried beside the lines rather than worked out from an
// index: "" leaves the guest alone, "-" is the specification gvm writes from
// ~/.gvmrc, and anything else is the name of one the vCenter holds.
//
// The cursor starts on gvm's own road where the site is set up. A site whose
// domain, netmask and gateway have been written down is a site whose machines
// are meant to be given them — that is what writing them down was for — and the
// answer wanted nearly every time should not be the one that has to be arrowed
// to. Where there is no such road it starts at the top, on the answer that is
// never wrong: leave the guest as the template made it.
//
// Nothing is decided by the cursor sitting there. The picker chooses on Enter,
// the address is asked for after it, and the confirmation still says in full
// what will happen.
func customChoices(st site, specs []string, own bool) (lines, kinds []string, at int) {
lines = []string{"leave the guest as the template made it"}
kinds = []string{""}
if own {
// What it will do, not where the settings are kept: a picker line that
// is mostly an absolute path says nothing about the choice being made,
// and `gvm config` is where the file is named.
lines = append(lines, SF("this site — %s, gateway %s", st.domain, st.gateway))
kinds = append(kinds, "-")
at = len(lines) - 1
}
for _, name := range specs {
lines = append(lines, name)
kinds = append(kinds, name)
}
return lines, kinds, at
}
// deployAddress asks for the address, which is the other half of what a
// specification cannot know: it holds the netmask, the gateway and the domain,
// and the machine holds its own number.
+37
View File
@@ -571,6 +571,43 @@ func TestTheSiteComesOutOfTheConfiguration(t *testing.T) {
}
}
// The picker opens on the road gvm writes itself where the site has been set
// up: a domain, a netmask and a gateway in ~/.gvmrc are there so that machines
// get them, and the answer wanted nearly every time must not be the one that
// has to be arrowed to. Where there is no such road it opens at the top, on the
// answer that is never wrong.
func TestTheCustomisationPickerOpensOnTheSiteRoad(t *testing.T) {
st := testSite()
lines, kinds, at := customChoices(st, []string{"linux-static", "windows"}, true)
if len(lines) != 4 || len(kinds) != len(lines) {
t.Fatalf("the picker offers %d lines and %d meanings: %v", len(lines), len(kinds), lines)
}
if kinds[at] != "-" {
t.Errorf("it opens on %q, which is %q — want gvm's own road", lines[at], kinds[at])
}
// And that line says what it will do, in the site's own words: a default
// nobody can read is one nobody knows they are taking.
if !strings.Contains(lines[at], st.domain) || !strings.Contains(lines[at], st.gateway) {
t.Errorf("the line it opens on is %q, want the domain and the gateway in it", lines[at])
}
// The vCenter's own are still there, after it and in order.
if lines[2] != "linux-static" || lines[3] != "windows" {
t.Errorf("the vCenter's specifications came out as %v", lines[2:])
}
// No site, and the cursor is at the top rather than on somebody else's
// specification: leaving the guest as the template made it is the one
// answer that cannot be wrong.
lines, kinds, at = customChoices(site{}, []string{"linux-static"}, false)
if at != 0 || kinds[at] != "" {
t.Errorf("with no site it opens on %q (%q)", lines[at], kinds[at])
}
if len(lines) != 2 {
t.Errorf("with no site the picker offers %v", lines)
}
}
// The question that asks for an address has to read as a sentence on both
// roads. It did not: the interactive half settles which road before it asks,
// so at that moment every field is still empty — which read as "no
+1 -1
View File
@@ -1 +1 @@
1.3.9
1.3.10