Work in progress to automate doc generation

This commit is contained in:
Pieter Noordhuis 2024-05-21 15:23:44 +02:00
parent a014d50a6a
commit 53289dabdd
No known key found for this signature in database
GPG Key ID: 12ACCCC104CF2930
8 changed files with 330 additions and 0 deletions

58
experimental/docs/docs.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/databricks/databricks-sdk-go/service/serving"
)
func PromptMessage() ([]serving.ChatMessage, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
dir := filepath.Join(home, "emu/docs/source/dev-tools/cli")
files, err := filepath.Glob(filepath.Join(dir, "*.md"))
if err != nil {
return nil, err
}
messages := []string{`
You're helping write documentation for the Databricks CLI.
Forget everything you know about the Databricks CLI and start from scratch.
You'll be provided with all the information you need to write the documentation.
Example invocations must be wrapped in Markdown code blocks.
What follows is existing official documentation for the Databricks CLI.
`}
ignore := []string{
"completion-commands.md",
"migrate.md",
"bundle-commands.md",
}
for _, file := range files {
if slices.Contains(ignore, filepath.Base(file)) {
continue
}
contents, err := os.ReadFile(file)
if err != nil {
return nil, err
}
messages = append(messages, fmt.Sprintf("File %s:\n\n------------%s\n------------", file, string(contents)))
}
return []serving.ChatMessage{
{
Role: serving.ChatMessageRoleSystem,
Content: strings.Join(messages, "\n\n"),
},
}, nil
}

View File

@ -0,0 +1,60 @@
package main
import (
"context"
"fmt"
"github.com/databricks/cli/cmd"
"github.com/spf13/cobra"
)
type Group struct {
Name string
Package string
Command *cobra.Command
Subcommands []*cobra.Command
}
func Find(name string) *Group {
root := cmd.New(context.Background())
for _, c := range root.Commands() {
if c.Use != name {
continue
}
return &Group{
Name: name,
Package: c.Annotations["package"],
Command: c,
Subcommands: c.Commands(),
}
}
return nil
}
func (g *Group) Prompt() string {
msg := fmt.Sprintf(`
We're authoring documentation and examples for the "%s" command group.
All output must be valid Markdown.
Do not include expected command output; you don't know.
Every command has its own Markdown header.
The documentation should be written in Markdown, with code blocks for each command invocation.
By concatenating the code blocks, you should be able to run the script and see the output.
Below is the help output of each one of the commands.
`, Invocation(g.Command))
sep := "SEPARATOR BETWEEN INSTRUCTION AND HELP OUTPUT"
all := append([]*cobra.Command{g.Command}, g.Subcommands...)
for _, c := range all {
inv := Invocation(c)
msg += "\n\n" + sep + "\n\n$ " + inv + " --help\n\n" + CaptureHelp(c)
}
msg += "\n\n" + sep + "\n\n"
return msg
}

View File

@ -0,0 +1,18 @@
package main_test
import (
"testing"
main "github.com/databricks/cli/experimental/docs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGroupFind(t *testing.T) {
g := main.Find("catalogs")
require.NotNil(t, g)
assert.Equal(t, "catalogs", g.Name)
assert.Equal(t, "catalogs", g.Command.Use)
assert.Len(t, g.Subcommands, 5)
g.Command.CalledAs()
}

30
experimental/docs/help.go Normal file
View File

@ -0,0 +1,30 @@
package main
import (
"bytes"
"slices"
"strings"
"github.com/spf13/cobra"
)
func CaptureHelp(cmd *cobra.Command) string {
var buf bytes.Buffer
cmd.SetOut(&buf)
if err := cmd.Help(); err != nil {
panic(err)
}
return buf.String()
}
func Invocation(cmd *cobra.Command) string {
var args []string
for cmd != nil {
args = append(args, cmd.Use)
cmd = cmd.Parent()
}
slices.Reverse(args)
return strings.Join(args, " ")
}

View File

@ -0,0 +1,23 @@
package main_test
import (
"context"
"testing"
"github.com/databricks/cli/cmd"
main "github.com/databricks/cli/experimental/docs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCaptureHelp(t *testing.T) {
c := cmd.New(context.Background())
h := main.CaptureHelp(c)
require.NotNil(t, h)
}
func TestInvocation(t *testing.T) {
g := main.Find("catalogs")
require.NotNil(t, g)
assert.Equal(t, "databricks catalogs", main.Invocation(g.Command))
}

89
experimental/docs/main.go Normal file
View File

@ -0,0 +1,89 @@
package main
import (
"context"
"fmt"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/service/serving"
)
func main() {
ctx := context.Background()
w, err := databricks.NewWorkspaceClient()
if err != nil {
panic(err)
}
messages, err := PromptMessage()
if err != nil {
panic(err)
}
// Please author Markdown documentation that first
// creates the necessary resources, demonstrates usage of the commands, and then
// finally cleans up the resources. Make sure to demonstrate usage of necessary flags. Remember to not delete
// the resource before demonstrating the other commands. If the final step is to delete the resource, then
// you can delete the resource at the end of the script.
g := Find("jobs")
contents := g.Prompt()
// contents += `
// The output should have the following structure:
// # <command group>
// <prose description of the command group>
// ## Quick start
// BEGIN
// <
// Markdown documentation that first creates the necessary resources, demonstrates usage of the commands, and then
// finally cleans up the resources. Make sure to demonstrate usage of necessary flags. Remember to not delete
// the resource before demonstrating the other commands. If the final step is to delete the resource, then
// you can delete the resource at the end of the script.
// Each command is a separate code block with prose separating them.
// Do not include command output; you don't know.
// Never use placeholders in the commands, but use for example "my_catalog" for a "CATALOG_NAME" placeholder.
// All code blocks concatenated should be runnable as a bash script.
// If commands depend on pre-existing resources, do not include the commands to create or destroy them,
// but call out this requirement in a comment.
// >
// END
// `
// `
// ## Commands
// <Markdown headers for each command, with the command name as the header text>
// `
contents += `
Output an executable Bash script that demonstrates how to use these commands.
Insert comments where you expect the user to have pre-existing resources.
`
messages = append(messages, serving.ChatMessage{
Role: serving.ChatMessageRoleUser,
Content: contents,
})
res, err := w.ServingEndpoints.Query(ctx, serving.QueryEndpointInput{
Name: "databricks-dbrx-instruct",
Messages: messages,
})
if err != nil {
panic(err)
}
// enc := json.NewEncoder(os.Stdout)
// enc.SetIndent("", " ")
// enc.Encode(res)
fmt.Printf("Output: %s\n", res.Choices[0].Message.Content)
}

View File

@ -0,0 +1,39 @@
package main
import (
"context"
"github.com/databricks/cli/cmd"
"golang.org/x/exp/maps"
)
type Package struct {
Name string
Groups []*Group
}
func Packages() []Package {
root := cmd.New(context.Background())
packages := make(map[string]Package)
for _, c := range root.Commands() {
pkg := c.Annotations["package"]
if pkg == "" {
continue
}
g := Find(c.Use)
p, ok := packages[pkg]
if !ok {
p = Package{
Name: pkg,
Groups: []*Group{g},
}
} else {
p.Groups = append(p.Groups, g)
}
packages[pkg] = p
}
return maps.Values(packages)
}

View File

@ -0,0 +1,13 @@
package main_test
import (
"testing"
main "github.com/databricks/cli/experimental/docs"
"github.com/stretchr/testify/require"
)
func TestPackagesAll(t *testing.T) {
pkgs := main.Packages()
require.NotEmpty(t, pkgs)
}