Skip to content

Commit

Permalink
refactor: switch to TBD54566975/scaffolder
Browse files Browse the repository at this point in the history
  • Loading branch information
alecthomas committed Oct 30, 2023
1 parent 0648583 commit c1c8b4c
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 113 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/TBD54566975/ftl

go 1.21
go 1.21.3

require (
connectrpc.com/connect v1.11.1
Expand Down Expand Up @@ -59,6 +59,7 @@ require (
require (
connectrpc.com/grpcreflect v1.2.0
connectrpc.com/otelconnect v0.5.0
github.com/TBD54566975/scaffolder v0.0.0-20231026011825-304b47f65b46
github.com/alecthomas/assert/v2 v2.3.0
github.com/alecthomas/atomic v0.1.0-alpha2
github.com/alecthomas/concurrency v0.0.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 15 additions & 112 deletions internal/scaffolder.go
Original file line number Diff line number Diff line change
@@ -1,128 +1,31 @@
package internal

import (
"io/fs"
"os"
"path/filepath"
"reflect"
"strings"
"text/template"

"github.com/alecthomas/errors"
"github.com/iancoleman/strcase"

"github.com/TBD54566975/scaffolder"
)

// Scaffold evaluates the scaffolding files at the given destination against
// ctx.
//
// Both paths and file contents are evaluated.
//
// If a file name ends with ".tmpl", the ".tmpl" suffix is removed.
//
// 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()
if err != nil {
return errors.WithStack(err)
}

if strings.HasSuffix(path, ".tmpl") {
newPath := strings.TrimSuffix(path, ".tmpl")
if err = os.Rename(path, newPath); err != nil {
return errors.Wrap(err, "failed to rename file")
}
path = newPath
}

// Evaluate the last component of path name templates.
dir := filepath.Dir(path)
base := filepath.Base(path)
newName, err := evaluate(base, ctx)
if err != nil {
return errors.Wrapf(err, "%s", path)
}
// Rename if necessary.
if newName != base {
newName = filepath.Join(dir, newName)
err = os.Rename(path, newName)
if err != nil {
return errors.Wrap(err, "failed to rename file")
}
path = newName
}

if !info.Mode().IsRegular() {
return nil
}

// Evaluate file content.
template, err := os.ReadFile(path)
if err != nil {
return errors.Wrapf(err, "%s", path)
}
content, err := evaluate(string(template), ctx)
if err != nil {
return errors.Wrapf(err, "%s", path)
}
err = os.WriteFile(path, []byte(content), info.Mode())
if err != nil {
return errors.Wrapf(err, "%s", path)
}
return nil
}))
}

// Walk dir executing fn after each entry.
func walkDir(dir string, fn func(path string, d fs.DirEntry) error) error {
entries, err := os.ReadDir(dir)
if err != nil {
return errors.WithStack(err)
}
for _, entry := range entries {
if entry.IsDir() {
err = walkDir(filepath.Join(dir, entry.Name()), fn)
if err != nil {
return errors.WithStack(err)
}
}
err = fn(filepath.Join(dir, entry.Name()), entry)
if err != nil {
return errors.WithStack(err)
}
}
return nil
}

func evaluate(tmpl string, ctx any) (string, error) {
t, err := template.New("scaffolding").Funcs(
template.FuncMap{
"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()
},
return errors.WithStack(scaffolder.Scaffold(destination, ctx, scaffolder.Functions(template.FuncMap{
"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()
},
).Parse(tmpl)
if err != nil {
return "", errors.Wrap(err, "failed to parse template")
}
newName := &strings.Builder{}
err = t.Execute(newName, ctx)
if err != nil {
return "", errors.Wrap(err, "failed to execute template")
}
return newName.String(), nil
})))
}

0 comments on commit c1c8b4c

Please sign in to comment.