diff --git a/cmd/ftl/cmd_init.go b/cmd/ftl/cmd_init.go index 583199af11..1982580adf 100644 --- a/cmd/ftl/cmd_init.go +++ b/cmd/ftl/cmd_init.go @@ -1,6 +1,7 @@ package main import ( + "archive/zip" "os" "path/filepath" @@ -18,28 +19,15 @@ type initCmd struct { } type initGoCmd struct { - Dir string `arg:"" default:"." type:"dir" help:"Directory to initialize the module in."` - Name string `short:"n" help:"Name of the FTL module (defaults to name of directory)."` - GoModule string `short:"G" required:"" help:"Go module import path."` + Dir string `arg:"" default:"." type:"dir" help:"Directory to initialize the module in."` + Name string `short:"n" help:"Name of the FTL module (defaults to name of directory)."` } func (i initGoCmd) Run(parent *initCmd) error { if i.Name == "" { i.Name = filepath.Base(i.Dir) } - err := internal.UnzipDir(goruntime.Files, i.Dir) - if err != nil { - return errors.WithStack(err) - } - if err := internal.Scaffold(i.Dir, i); err != nil { - return errors.WithStack(err) - } - if !parent.Hermit { - if err := os.RemoveAll(filepath.Join(i.Dir, "bin")); err != nil { - return errors.WithStack(err) - } - } - return nil + return errors.WithStack(scaffold(goruntime.Files, parent.Hermit, i.Dir, i)) } type initKotlinCmd struct { @@ -51,15 +39,22 @@ func (i *initKotlinCmd) Run(parent *initCmd) error { if i.Name == "" { i.Name = filepath.Base(i.Dir) } - err := internal.UnzipDir(kotlinruntime.Files, i.Dir) + return errors.WithStack(scaffold(kotlinruntime.Files, parent.Hermit, i.Dir, i)) +} + +func scaffold(reader *zip.Reader, hermit bool, dir string, ctx any) error { + err := internal.UnzipDir(reader, dir) if err != nil { return errors.WithStack(err) } - if err := internal.Scaffold(i.Dir, i); err != nil { + if err := os.Remove(filepath.Join(dir, "go.mod")); err != nil { + return errors.WithStack(err) + } + if err := internal.Scaffold(dir, ctx); err != nil { return errors.WithStack(err) } - if !parent.Hermit { - if err := os.RemoveAll(filepath.Join(i.Dir, "bin")); err != nil { + if !hermit { + if err := os.RemoveAll(filepath.Join(dir, "bin")); err != nil { return errors.WithStack(err) } } diff --git a/go-runtime/scaffolding/README.md b/go-runtime/scaffolding/README.md new file mode 100644 index 0000000000..26c2a52ac1 --- /dev/null +++ b/go-runtime/scaffolding/README.md @@ -0,0 +1,17 @@ +# FTL modules + +Each subdirectory represents an FTL module. Remote modules will be +code-generated into their own directories, one module per directory. Note that +this is a temporary solution. + +For example given an `echo` module written in Go that calls a `time` module +written in Kotlin, the filesystem might look like this once the FTL tooling is +started: + +``` +README.md +go.mod +echo/ftl.toml +echo/echo.go +time/generated_ftl_module.go +``` diff --git a/go-runtime/scaffolding/ftl.toml b/go-runtime/scaffolding/ftl.toml deleted file mode 100644 index 99a78b688e..0000000000 --- a/go-runtime/scaffolding/ftl.toml +++ /dev/null @@ -1,2 +0,0 @@ -module = "{{ .Name | lower }}" -language = "go" diff --git a/go-runtime/scaffolding/go.mod b/go-runtime/scaffolding/go.mod deleted file mode 100644 index 3484412613..0000000000 --- a/go-runtime/scaffolding/go.mod +++ /dev/null @@ -1,4 +0,0 @@ -// This needs to exist so that the Go toolchain doesn't include this directory. Annoying. -module exclude - -go 1.21.3 diff --git a/go-runtime/scaffolding/go.mod.tmpl b/go-runtime/scaffolding/go.mod.tmpl index b9217717e7..32a424bf24 100644 --- a/go-runtime/scaffolding/go.mod.tmpl +++ b/go-runtime/scaffolding/go.mod.tmpl @@ -1,4 +1,4 @@ -module {{ .GoModule }} +module ftl go 1.21.0 diff --git a/go-runtime/scaffolding/{{ .Name | camel | lower }}/ftl.toml b/go-runtime/scaffolding/{{ .Name | camel | lower }}/ftl.toml new file mode 100644 index 0000000000..8afdcd4eee --- /dev/null +++ b/go-runtime/scaffolding/{{ .Name | camel | lower }}/ftl.toml @@ -0,0 +1,3 @@ +module = "{{ .Name }}" +language = "go" +deploy = ["build/main", "build/schema.pb"] diff --git a/go-runtime/scaffolding/{{ .Name | camel | lower }}/go.mod b/go-runtime/scaffolding/{{ .Name | camel | lower }}/go.mod new file mode 100644 index 0000000000..c6769a5cb3 --- /dev/null +++ b/go-runtime/scaffolding/{{ .Name | camel | lower }}/go.mod @@ -0,0 +1,5 @@ +module ftl/{{ .Name | camel | lower }} + +go 1.21.0 + +require github.com/TBD54566975/ftl latest \ No newline at end of file diff --git a/go-runtime/scaffolding/{{ .Name | camel | lower }}/{{ .Name | camel | lower }}.go.tmpl b/go-runtime/scaffolding/{{ .Name | camel | lower }}/{{ .Name | camel | lower }}.go.tmpl new file mode 100644 index 0000000000..c30fe15a54 --- /dev/null +++ b/go-runtime/scaffolding/{{ .Name | camel | lower }}/{{ .Name | camel | lower }}.go.tmpl @@ -0,0 +1,21 @@ +package {{ .Name | camel | lower }} + +import ( + "context" + "fmt" + + _ "github.com/TBD54566975/ftl/go-runtime/sdk" // Import the FTL SDK. +) + +type EchoRequest struct { + Name string `json:"name'` +} + +type EchoResponse struct { + Message string `json:"message"` +} + +//ftl:verb +func echo(ctx context.Context, req EchoRequest) (EchoResponse, error) { + return EchoResponse{Message: fmt.Sprintf("Hello, %s!", req.Name)}, nil +} \ No newline at end of file diff --git a/go-runtime/scaffolding/{{ .Name | lower }}.go.tmpl b/go-runtime/scaffolding/{{ .Name | lower }}.go.tmpl deleted file mode 100644 index 0cea72ac6c..0000000000 --- a/go-runtime/scaffolding/{{ .Name | lower }}.go.tmpl +++ /dev/null @@ -1,18 +0,0 @@ -//ftl:module {{ .Name | lower }} -package {{ .Name | lower }} - -import ( - "context" - _ "github.com/TBD54566975/ftl/go-runtime/sdk" // Import the FTL SDK. -) - -type {{ .Name | camel }}Request struct { -} - -type {{ .Name | camel }}Response struct { -} - -//ftl:verb -func {{ .Name | camel }}(ctx context.Context, req {{ .Name | camel }}Request) ({{ .Name | camel }}Response, error) { - return {{ .Name | camel }}Response{}, nil -} \ No newline at end of file diff --git a/internal/scaffolder.go b/internal/scaffolder.go index 48b90bbd43..3035e87ec3 100644 --- a/internal/scaffolder.go +++ b/internal/scaffolder.go @@ -21,6 +21,10 @@ import ( // // The functions "snake", "camel", "lowerCamel", "kebab", "upper", and "lower" // are available. +// +// This is inspired by [cookiecutter]. +// +// [cookiecutter]: https://github.com/cookiecutter/cookiecutter func Scaffold(destination string, ctx any) error { return errors.WithStack(walkDir(destination, func(path string, d fs.DirEntry) error { info, err := d.Info() @@ -98,12 +102,15 @@ func walkDir(dir string, fn func(path string, d fs.DirEntry) error) error { func evaluate(tmpl string, ctx any) (string, error) { t, err := template.New("scaffolding").Funcs( template.FuncMap{ - "snake": strcase.ToSnake, - "camel": strcase.ToCamel, - "lowerCamel": strcase.ToLowerCamel, - "kebab": strcase.ToKebab, - "upper": strings.ToUpper, - "lower": strings.ToLower, + "snake": strcase.ToSnake, + "screamingSnake": strcase.ToScreamingSnake, + "camel": strcase.ToCamel, + "lowerCamel": strcase.ToLowerCamel, + "kebab": strcase.ToKebab, + "screamingKebab": strcase.ToScreamingKebab, + "upper": strings.ToUpper, + "lower": strings.ToLower, + "title": strings.Title, "typename": func(v any) string { return reflect.Indirect(reflect.ValueOf(v)).Type().Name() },