From 5278af1d9f2f9fa6a9968d384f6184ad7d9aebd2 Mon Sep 17 00:00:00 2001
From: Lennart Kats <lennart.kats@databricks.com>
Date: Mon, 11 Dec 2023 10:49:11 +0100
Subject: [PATCH 1/5] Improve support for multiple builtin templates

---
 cmd/bundle/init.go      | 36 ++++++++++++++++++++++++++++++------
 cmd/bundle/init_test.go |  8 --------
 libs/cmdio/io.go        | 26 ++++++++++++++------------
 3 files changed, 44 insertions(+), 26 deletions(-)

diff --git a/cmd/bundle/init.go b/cmd/bundle/init.go
index ac6f49de38..8aa8f6cc30 100644
--- a/cmd/bundle/init.go
+++ b/cmd/bundle/init.go
@@ -30,14 +30,18 @@ type nativeTemplate struct {
 var nativeTemplates = []nativeTemplate{
 	{
 		name:        "default-python",
-		description: "The default Python template",
+		description: "The default Python template for Notebooks / Delta Live Tables / Workflows",
 	},
 	{
 		name:        "mlops-stacks",
 		gitUrl:      "https://github.com/databricks/mlops-stacks",
-		description: "The Databricks MLOps Stacks template (https://github.com/databricks/mlops-stacks)",
+		description: "The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)",
 		aliases:     []string{"mlops-stack"},
 	},
+	{
+		name:        "custom...",
+		description: "Bring your own template",
+	},
 }
 
 func nativeTemplateDescriptions() string {
@@ -48,14 +52,27 @@ func nativeTemplateDescriptions() string {
 	return strings.Join(lines, "\n")
 }
 
-func nativeTemplateOptions() []string {
-	names := make([]string, 0, len(nativeTemplates))
+func nativeTemplateOptions() []cmdio.Tuple {
+	names := make([]cmdio.Tuple, 0, len(nativeTemplates))
 	for _, template := range nativeTemplates {
-		names = append(names, template.name)
+		tuple := cmdio.Tuple{
+			Name: template.name,
+			Id:   template.description,
+		}
+		names = append(names, tuple)
 	}
 	return names
 }
 
+func getNativeTemplateByDescription(description string) string {
+	for _, template := range nativeTemplates {
+		if template.description == description {
+			return template.name
+		}
+	}
+	return ""
+}
+
 func getUrlForNativeTemplate(name string) string {
 	for _, template := range nativeTemplates {
 		if template.name == name {
@@ -134,10 +151,17 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf
 			if !cmdio.IsOutTTY(ctx) || !cmdio.IsInTTY(ctx) {
 				return errors.New("please specify a template")
 			}
-			templatePath, err = cmdio.AskSelect(ctx, "Template to use", nativeTemplateOptions())
+			description, err := cmdio.SelectOrdered(ctx, nativeTemplateOptions(), "Template to use")
 			if err != nil {
 				return err
 			}
+			templatePath = getNativeTemplateByDescription(description)
+		}
+
+		if templatePath == "custom..." {
+			cmdio.LogString(ctx, "Please specify a path or Git repository to use a custom template.")
+			cmdio.LogString(ctx, "See https://docs.databricks.com/en/dev-tools/bundles/templates.html to learn more about custom templates.")
+			return nil
 		}
 
 		// Expand templatePath to a git URL if it's an alias for a known native template
diff --git a/cmd/bundle/init_test.go b/cmd/bundle/init_test.go
index db4446bb1c..f49a37a8cf 100644
--- a/cmd/bundle/init_test.go
+++ b/cmd/bundle/init_test.go
@@ -26,14 +26,6 @@ func TestBundleInitRepoName(t *testing.T) {
 	assert.Equal(t, "www.github.com", repoName("https://www.github.com"))
 }
 
-func TestNativeTemplateOptions(t *testing.T) {
-	assert.Equal(t, []string{"default-python", "mlops-stacks"}, nativeTemplateOptions())
-}
-
-func TestNativeTemplateDescriptions(t *testing.T) {
-	assert.Equal(t, "- default-python: The default Python template\n- mlops-stacks: The Databricks MLOps Stacks template (https://github.com/databricks/mlops-stacks)", nativeTemplateDescriptions())
-}
-
 func TestGetUrlForNativeTemplate(t *testing.T) {
 	assert.Equal(t, "https://github.com/databricks/mlops-stacks", getUrlForNativeTemplate("mlops-stacks"))
 	assert.Equal(t, "https://github.com/databricks/mlops-stacks", getUrlForNativeTemplate("mlops-stack"))
diff --git a/libs/cmdio/io.go b/libs/cmdio/io.go
index cf405a7a49..2fb141c261 100644
--- a/libs/cmdio/io.go
+++ b/libs/cmdio/io.go
@@ -130,19 +130,13 @@ func RenderReader(ctx context.Context, r io.Reader) error {
 	}
 }
 
-type tuple struct{ Name, Id string }
+type Tuple struct{ Name, Id string }
 
-func (c *cmdIO) Select(names map[string]string, label string) (id string, err error) {
+func (c *cmdIO) Select(items []Tuple, label string) (id string, err error) {
 	if !c.interactive {
 		return "", fmt.Errorf("expected to have %s", label)
 	}
-	var items []tuple
-	for k, v := range names {
-		items = append(items, tuple{k, v})
-	}
-	slices.SortFunc(items, func(a, b tuple) int {
-		return strings.Compare(a.Name, b.Name)
-	})
+
 	idx, _, err := (&promptui.Select{
 		Label:             label,
 		Items:             items,
@@ -167,11 +161,19 @@ func (c *cmdIO) Select(names map[string]string, label string) (id string, err er
 
 func Select[V any](ctx context.Context, names map[string]V, label string) (id string, err error) {
 	c := fromContext(ctx)
-	stringNames := map[string]string{}
+	var items []Tuple
 	for k, v := range names {
-		stringNames[k] = fmt.Sprint(v)
+		items = append(items, Tuple{k, fmt.Sprint(v)})
 	}
-	return c.Select(stringNames, label)
+	slices.SortFunc(items, func(a, b Tuple) int {
+		return strings.Compare(a.Name, b.Name)
+	})
+	return c.Select(items, label)
+}
+
+func SelectOrdered(ctx context.Context, items []Tuple, label string) (id string, err error) {
+	c := fromContext(ctx)
+	return c.Select(items, label)
 }
 
 func (c *cmdIO) Secret(label string) (value string, err error) {

From 354c642319b38abf207fc4a8a59d9a03e0edf2de Mon Sep 17 00:00:00 2001
From: Lennart Kats <lennart.kats@databricks.com>
Date: Wed, 20 Dec 2023 12:18:57 +0100
Subject: [PATCH 2/5] Use a const

---
 cmd/bundle/init.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/cmd/bundle/init.go b/cmd/bundle/init.go
index 8aa8f6cc30..5602c82906 100644
--- a/cmd/bundle/init.go
+++ b/cmd/bundle/init.go
@@ -27,6 +27,8 @@ type nativeTemplate struct {
 	aliases     []string
 }
 
+const customTemplate = "custom..."
+
 var nativeTemplates = []nativeTemplate{
 	{
 		name:        "default-python",
@@ -39,7 +41,7 @@ var nativeTemplates = []nativeTemplate{
 		aliases:     []string{"mlops-stack"},
 	},
 	{
-		name:        "custom...",
+		name:        customTemplate,
 		description: "Bring your own template",
 	},
 }
@@ -158,7 +160,7 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf
 			templatePath = getNativeTemplateByDescription(description)
 		}
 
-		if templatePath == "custom..." {
+		if templatePath == customTemplate {
 			cmdio.LogString(ctx, "Please specify a path or Git repository to use a custom template.")
 			cmdio.LogString(ctx, "See https://docs.databricks.com/en/dev-tools/bundles/templates.html to learn more about custom templates.")
 			return nil

From d58811fed59f748fcaef77886f56ea23e8ecce70 Mon Sep 17 00:00:00 2001
From: Lennart Kats <lennart.kats@databricks.com>
Date: Sat, 23 Dec 2023 17:38:00 +0100
Subject: [PATCH 3/5] Process feedback

---
 cmd/bundle/init.go      |  6 +++++-
 cmd/bundle/init_test.go | 16 ++++++++++++++++
 libs/cmdio/io.go        |  4 ++++
 3 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/cmd/bundle/init.go b/cmd/bundle/init.go
index bc4cebef1e..d1f95028dc 100644
--- a/cmd/bundle/init.go
+++ b/cmd/bundle/init.go
@@ -46,14 +46,18 @@ var nativeTemplates = []nativeTemplate{
 	},
 }
 
+// Return template descriptions for command-line help
 func nativeTemplateDescriptions() string {
 	var lines []string
 	for _, template := range nativeTemplates {
-		lines = append(lines, fmt.Sprintf("- %s: %s", template.name, template.description))
+		if template.name != customTemplate {
+			lines = append(lines, fmt.Sprintf("- %s: %s", template.name, template.description))
+		}
 	}
 	return strings.Join(lines, "\n")
 }
 
+// Return template options for an interactive prompt
 func nativeTemplateOptions() []cmdio.Tuple {
 	names := make([]cmdio.Tuple, 0, len(nativeTemplates))
 	for _, template := range nativeTemplates {
diff --git a/cmd/bundle/init_test.go b/cmd/bundle/init_test.go
index f49a37a8cf..01343f870e 100644
--- a/cmd/bundle/init_test.go
+++ b/cmd/bundle/init_test.go
@@ -3,6 +3,7 @@ package bundle
 import (
 	"testing"
 
+	"github.com/databricks/cli/libs/cmdio"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -26,6 +27,21 @@ func TestBundleInitRepoName(t *testing.T) {
 	assert.Equal(t, "www.github.com", repoName("https://www.github.com"))
 }
 
+func TestNativeTemplateOptions(t *testing.T) {
+	expected := []cmdio.Tuple{
+		{Name: "default-python", Id: "The default Python template for Notebooks / Delta Live Tables / Workflows"},
+		{Name: "mlops-stacks", Id: "The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)"},
+		{Name: "custom...", Id: "Bring your own template"},
+	}
+	assert.Equal(t, expected, nativeTemplateOptions())
+}
+
+func TestNativeTemplateDescriptions(t *testing.T) {
+	expected := `- default-python: The default Python template for Notebooks / Delta Live Tables / Workflows
+- mlops-stacks: The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)`
+	assert.Equal(t, expected, nativeTemplateDescriptions())
+}
+
 func TestGetUrlForNativeTemplate(t *testing.T) {
 	assert.Equal(t, "https://github.com/databricks/mlops-stacks", getUrlForNativeTemplate("mlops-stacks"))
 	assert.Equal(t, "https://github.com/databricks/mlops-stacks", getUrlForNativeTemplate("mlops-stack"))
diff --git a/libs/cmdio/io.go b/libs/cmdio/io.go
index 1e1345bdae..9d95611a4e 100644
--- a/libs/cmdio/io.go
+++ b/libs/cmdio/io.go
@@ -184,6 +184,8 @@ func (c *cmdIO) Select(items []Tuple, label string) (id string, err error) {
 	return
 }
 
+// Show a selection prompt where the user can pick one of the name/id items.
+// The items are sorted alphabetically by name.
 func Select[V any](ctx context.Context, names map[string]V, label string) (id string, err error) {
 	c := fromContext(ctx)
 	var items []Tuple
@@ -196,6 +198,8 @@ func Select[V any](ctx context.Context, names map[string]V, label string) (id st
 	return c.Select(items, label)
 }
 
+// Show a selection prompt where the user can pick one of the name/id items.
+// The items can appear in a custom order and are not alphabetically sorted.
 func SelectOrdered(ctx context.Context, items []Tuple, label string) (id string, err error) {
 	c := fromContext(ctx)
 	return c.Select(items, label)

From d01eaf426c0b194c8d5859b62f8ff6d59a6e0b54 Mon Sep 17 00:00:00 2001
From: Lennart Kats <lennart.kats@databricks.com>
Date: Sat, 23 Dec 2023 17:39:50 +0100
Subject: [PATCH 4/5] Clarify name

---
 cmd/bundle/init.go      | 4 ++--
 cmd/bundle/init_test.go | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/cmd/bundle/init.go b/cmd/bundle/init.go
index d1f95028dc..db8250d071 100644
--- a/cmd/bundle/init.go
+++ b/cmd/bundle/init.go
@@ -47,7 +47,7 @@ var nativeTemplates = []nativeTemplate{
 }
 
 // Return template descriptions for command-line help
-func nativeTemplateDescriptions() string {
+func nativeTemplateHelpDescriptions() string {
 	var lines []string
 	for _, template := range nativeTemplates {
 		if template.name != customTemplate {
@@ -122,7 +122,7 @@ TEMPLATE_PATH optionally specifies which template to use. It can be one of the f
 - a local file system path with a template directory
 - a Git repository URL, e.g. https://github.com/my/repository
 
-See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates.`, nativeTemplateDescriptions()),
+See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates.`, nativeTemplateHelpDescriptions()),
 	}
 
 	var configFile string
diff --git a/cmd/bundle/init_test.go b/cmd/bundle/init_test.go
index 01343f870e..aa89915968 100644
--- a/cmd/bundle/init_test.go
+++ b/cmd/bundle/init_test.go
@@ -36,10 +36,10 @@ func TestNativeTemplateOptions(t *testing.T) {
 	assert.Equal(t, expected, nativeTemplateOptions())
 }
 
-func TestNativeTemplateDescriptions(t *testing.T) {
+func TestNativeTemplateHelpDescriptions(t *testing.T) {
 	expected := `- default-python: The default Python template for Notebooks / Delta Live Tables / Workflows
 - mlops-stacks: The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)`
-	assert.Equal(t, expected, nativeTemplateDescriptions())
+	assert.Equal(t, expected, nativeTemplateHelpDescriptions())
 }
 
 func TestGetUrlForNativeTemplate(t *testing.T) {

From 885efa6385702070ab06837d1413e1e91b8eec34 Mon Sep 17 00:00:00 2001
From: "Lennart Kats (databricks)" <lennart.kats@databricks.com>
Date: Wed, 27 Dec 2023 12:58:35 +0100
Subject: [PATCH 5/5] Update libs/cmdio/io.go

Co-authored-by: shreyas-goenka <88374338+shreyas-goenka@users.noreply.github.com>
---
 libs/cmdio/io.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libs/cmdio/io.go b/libs/cmdio/io.go
index 9d95611a4e..d20991a7c2 100644
--- a/libs/cmdio/io.go
+++ b/libs/cmdio/io.go
@@ -199,7 +199,7 @@ func Select[V any](ctx context.Context, names map[string]V, label string) (id st
 }
 
 // Show a selection prompt where the user can pick one of the name/id items.
-// The items can appear in a custom order and are not alphabetically sorted.
+// The items appear in the order specified in the "items" argument.
 func SelectOrdered(ctx context.Context, items []Tuple, label string) (id string, err error) {
 	c := fromContext(ctx)
 	return c.Select(items, label)
