mirror of https://github.com/pressly/goose.git
commit
346605b722
|
@ -1,7 +1,7 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.8
|
||||
- 1.12
|
||||
|
||||
install:
|
||||
- go get github.com/golang/dep/cmd/dep
|
||||
|
|
77
create.go
77
create.go
|
@ -7,30 +7,48 @@ import (
|
|||
"path/filepath"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type tmplVars struct {
|
||||
Version string
|
||||
CamelName string
|
||||
}
|
||||
|
||||
// Create writes a new blank migration file.
|
||||
func CreateWithTemplate(db *sql.DB, dir string, migrationTemplate *template.Template, name, migrationType string) error {
|
||||
func CreateWithTemplate(db *sql.DB, dir string, tmpl *template.Template, name, migrationType string) error {
|
||||
version := time.Now().Format(timestampFormat)
|
||||
filename := fmt.Sprintf("%v_%v.%v", version, name, migrationType)
|
||||
filename := fmt.Sprintf("%v_%v.%v", version, snakeCase(name), migrationType)
|
||||
|
||||
fpath := filepath.Join(dir, filename)
|
||||
|
||||
tmpl := sqlMigrationTemplate
|
||||
if migrationType == "go" {
|
||||
tmpl = goSQLMigrationTemplate
|
||||
if tmpl == nil {
|
||||
if migrationType == "go" {
|
||||
tmpl = goSQLMigrationTemplate
|
||||
} else {
|
||||
tmpl = sqlMigrationTemplate
|
||||
}
|
||||
}
|
||||
|
||||
if migrationTemplate != nil {
|
||||
tmpl = migrationTemplate
|
||||
path := filepath.Join(dir, filename)
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "failed to create migration file")
|
||||
}
|
||||
|
||||
path, err := writeTemplateToFile(fpath, tmpl, version)
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to create migration file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
vars := tmplVars{
|
||||
Version: version,
|
||||
CamelName: camelCase(name),
|
||||
}
|
||||
if err := tmpl.Execute(f, vars); err != nil {
|
||||
return errors.Wrap(err, "failed to execute tmpl")
|
||||
}
|
||||
|
||||
log.Printf("Created new file: %s\n", path)
|
||||
log.Printf("Created new file: %s\n", f.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -39,33 +57,18 @@ func Create(db *sql.DB, dir, name, migrationType string) error {
|
|||
return CreateWithTemplate(db, dir, nil, name, migrationType)
|
||||
}
|
||||
|
||||
func writeTemplateToFile(path string, t *template.Template, version string) (string, error) {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("failed to create file: %v already exists", path)
|
||||
}
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = t.Execute(f, version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
var sqlMigrationTemplate = template.Must(template.New("goose.sql-migration").Parse(`-- +goose Up
|
||||
-- SQL in this section is executed when the migration is applied.
|
||||
-- +goose StatementBegin
|
||||
SELECT 'up SQL query';
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- SQL in this section is executed when the migration is rolled back.
|
||||
-- +goose StatementBegin
|
||||
SELECT 'down SQL query';
|
||||
-- +goose StatementEnd
|
||||
`))
|
||||
|
||||
var goSQLMigrationTemplate = template.Must(template.New("goose.go-migration").Parse(`package migration
|
||||
var goSQLMigrationTemplate = template.Must(template.New("goose.go-migration").Parse(`package migrations
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
@ -73,15 +76,15 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigration(Up{{.}}, Down{{.}})
|
||||
goose.AddMigration(up{{.CamelName}}, down{{.CamelName}})
|
||||
}
|
||||
|
||||
func Up{{.}}(tx *sql.Tx) error {
|
||||
func up{{.CamelName}}(tx *sql.Tx) error {
|
||||
// This code is executed when the migration is applied.
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down{{.}}(tx *sql.Tx) error {
|
||||
func down{{.CamelName}}(tx *sql.Tx) error {
|
||||
// This code is executed when the migration is rolled back.
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package goose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type camelSnakeStateMachine int
|
||||
|
||||
const ( // _$$_This is some text, OK?!
|
||||
idle camelSnakeStateMachine = iota // 0 ↑ ↑ ↑
|
||||
firstAlphaNum // 1 ↑ ↑ ↑ ↑ ↑
|
||||
alphaNum // 2 ↑↑↑ ↑ ↑↑↑ ↑↑↑ ↑
|
||||
delimiter // 3 ↑ ↑ ↑ ↑ ↑
|
||||
)
|
||||
|
||||
func (s camelSnakeStateMachine) next(r rune) camelSnakeStateMachine {
|
||||
switch s {
|
||||
case idle:
|
||||
if isAlphaNum(r) {
|
||||
return firstAlphaNum
|
||||
}
|
||||
case firstAlphaNum:
|
||||
if isAlphaNum(r) {
|
||||
return alphaNum
|
||||
}
|
||||
return delimiter
|
||||
case alphaNum:
|
||||
if !isAlphaNum(r) {
|
||||
return delimiter
|
||||
}
|
||||
case delimiter:
|
||||
if isAlphaNum(r) {
|
||||
return firstAlphaNum
|
||||
}
|
||||
return idle
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func camelCase(str string) string {
|
||||
var b strings.Builder
|
||||
|
||||
stateMachine := idle
|
||||
for i := 0; i < len(str); {
|
||||
r, size := utf8.DecodeRuneInString(str[i:])
|
||||
i += size
|
||||
stateMachine = stateMachine.next(r)
|
||||
switch stateMachine {
|
||||
case firstAlphaNum:
|
||||
b.WriteRune(unicode.ToUpper(r))
|
||||
case alphaNum:
|
||||
b.WriteRune(unicode.ToLower(r))
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func snakeCase(str string) string {
|
||||
var b bytes.Buffer
|
||||
|
||||
stateMachine := idle
|
||||
for i := 0; i < len(str); {
|
||||
r, size := utf8.DecodeRuneInString(str[i:])
|
||||
i += size
|
||||
stateMachine = stateMachine.next(r)
|
||||
switch stateMachine {
|
||||
case firstAlphaNum, alphaNum:
|
||||
b.WriteRune(unicode.ToLower(r))
|
||||
case delimiter:
|
||||
b.WriteByte('_')
|
||||
}
|
||||
}
|
||||
if stateMachine == idle {
|
||||
return string(bytes.TrimSuffix(b.Bytes(), []byte{'_'}))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func isAlphaNum(r rune) bool {
|
||||
return unicode.IsLetter(r) || unicode.IsNumber(r)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package goose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCamelSnake(t *testing.T) {
|
||||
tt := []struct {
|
||||
in string
|
||||
camel string
|
||||
snake string
|
||||
}{
|
||||
{in: "Add updated_at to users table", camel: "AddUpdatedAtToUsersTable", snake: "add_updated_at_to_users_table"},
|
||||
{in: "$()&^%(_--crazy__--input$)", camel: "CrazyInput", snake: "crazy_input"},
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
if got := camelCase(test.in); got != test.camel {
|
||||
t.Errorf("unexpected CamelCase for input(%q), got %q, want %q", test.in, got, test.camel)
|
||||
}
|
||||
if got := snakeCase(test.in); got != test.snake {
|
||||
t.Errorf("unexpected snake_case for input(%q), got %q, want %q", test.in, got, test.snake)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue