diff --git a/fix/anko_test.go b/fix/anko_test.go index f724d573..c843d578 100644 --- a/fix/anko_test.go +++ b/fix/anko_test.go @@ -11,7 +11,7 @@ import ( func Test_Anko(t *testing.T) { r := require.New(t) - box := packr.New("./fixtures", "./fixtures") + box := packr.New("./fixtures/anko", "./fixtures/anko") err := box.Walk(func(path string, info packr.File) error { if strings.HasPrefix(path, "pass") { t.Run(path, testPass(path, info)) diff --git a/fix/auto_timestamps_off.go b/fix/auto_timestamps_off.go new file mode 100644 index 00000000..7beb378d --- /dev/null +++ b/fix/auto_timestamps_off.go @@ -0,0 +1,55 @@ +package fix + +import ( + "strings" + + "github.com/gobuffalo/plush/ast" + "github.com/gobuffalo/plush/parser" +) + +// AutoTimestampsOff adds a t.Timestamps() statement to fizz migrations +// when they still use the implicit auto-timestamp old fizz feature. +func AutoTimestampsOff(content string) (string, error) { + var p *ast.Program + var err error + if p, err = parser.Parse("<% " + content + "%>"); err != nil { + return "", err + } + + var pt *ast.Program + if pt, err = parser.Parse("<% t.Timestamps() %>"); err != nil { + return "", err + } + ts := pt.Statements[0].(*ast.ExpressionStatement) + + for _, s := range p.Statements { + stmt := s.(*ast.ExpressionStatement) + if function, ok := stmt.Expression.(*ast.CallExpression); ok { + if function.Function.TokenLiteral() == "create_table" { + args := function.Arguments + enableTimestamps := true + if len(args) > 1 { + if v, ok := args[1].(*ast.HashLiteral); ok { + if strings.Contains(v.String(), `"timestamps": false`) { + enableTimestamps = false + } + } + } + for _, bs := range function.Block.Statements { + bstmt := bs.(*ast.ExpressionStatement) + if f, ok := bstmt.Expression.(*ast.CallExpression); ok { + fs := f.Function.String() + if fs == "t.DisableTimestamps" || fs == "t.Timestamps" { + enableTimestamps = false + } + } + } + if enableTimestamps { + function.Block.Statements = append(function.Block.Statements, ts) + } + } + } + } + + return p.String(), nil +} diff --git a/fix/auto_timestamps_off_test.go b/fix/auto_timestamps_off_test.go new file mode 100644 index 00000000..4438e635 --- /dev/null +++ b/fix/auto_timestamps_off_test.go @@ -0,0 +1,33 @@ +package fix + +import ( + "io/ioutil" + "strings" + "testing" + + packr "github.com/gobuffalo/packr/v2" + "github.com/stretchr/testify/require" +) + +func Test_AutoTimestampsOff(t *testing.T) { + r := require.New(t) + box := packr.New("./fixtures/auto_timestamps_off/raw", "./fixtures/auto_timestamps_off/raw") + boxPatched := packr.New("./fixtures/auto_timestamps_off/patched", "./fixtures/auto_timestamps_off/patched") + + err := box.Walk(func(path string, info packr.File) error { + t.Run(path, func(tt *testing.T) { + rr := require.New(tt) + b, err := ioutil.ReadAll(info) + rr.NoError(err) + + body := string(b) + patched, err := AutoTimestampsOff(body) + rr.NoError(err) + expected, err := boxPatched.FindString(path) + rr.NoError(err) + rr.Equal(strings.Replace(expected, "\r", "", -1), patched) + }) + return nil + }) + r.NoError(err) +} diff --git a/fix/fixtures/pass/0001_with_raw_backticks.anko.fizz b/fix/fixtures/anko/pass/0001_with_raw_backticks.anko.fizz similarity index 100% rename from fix/fixtures/pass/0001_with_raw_backticks.anko.fizz rename to fix/fixtures/anko/pass/0001_with_raw_backticks.anko.fizz diff --git a/fix/fixtures/pass/0002_happy.plush.fizz b/fix/fixtures/anko/pass/0002_happy.plush.fizz similarity index 100% rename from fix/fixtures/pass/0002_happy.plush.fizz rename to fix/fixtures/anko/pass/0002_happy.plush.fizz diff --git a/fix/fixtures/auto_timestamps_off/patched/0001_nominal.fizz b/fix/fixtures/auto_timestamps_off/patched/0001_nominal.fizz new file mode 100644 index 00000000..12329f68 --- /dev/null +++ b/fix/fixtures/auto_timestamps_off/patched/0001_nominal.fizz @@ -0,0 +1,11 @@ +create_table("users") { + t.Column("id", "int", {primary: true}) + t.Column("name", "string", {}) + t.Column("user_name", "string", {"size": 100}) + t.Column("alive", "boolean", {"null": true}) + t.Column("birth_date", "timestamp", {"null": true}) + t.Column("bio", "text", {"null": true}) + t.Column("price", "numeric", {"null": true, "default": "1.00"}) + t.Column("email", "string", {"default": "foo@example.com", "size": 50}) + t.Timestamps() +} diff --git a/fix/fixtures/auto_timestamps_off/patched/0002_with_disabled_option.fizz b/fix/fixtures/auto_timestamps_off/patched/0002_with_disabled_option.fizz new file mode 100644 index 00000000..1b456174 --- /dev/null +++ b/fix/fixtures/auto_timestamps_off/patched/0002_with_disabled_option.fizz @@ -0,0 +1,10 @@ +create_table("users_2", {"timestamps": false}) { + t.Column("id", "int", {primary: true}) + t.Column("name", "string", {}) + t.Column("user_name", "string", {"size": 100}) + t.Column("alive", "boolean", {"null": true}) + t.Column("birth_date", "timestamp", {"null": true}) + t.Column("bio", "text", {"null": true}) + t.Column("price", "numeric", {"null": true, "default": "1.00"}) + t.Column("email", "string", {"default": "foo@example.com", "size": 50}) +} diff --git a/fix/fixtures/auto_timestamps_off/patched/0003_with_disable_timestamps.fizz b/fix/fixtures/auto_timestamps_off/patched/0003_with_disable_timestamps.fizz new file mode 100644 index 00000000..15602cbd --- /dev/null +++ b/fix/fixtures/auto_timestamps_off/patched/0003_with_disable_timestamps.fizz @@ -0,0 +1,11 @@ +create_table("users_3") { + t.Column("id", "int", {primary: true}) + t.Column("name", "string", {}) + t.Column("user_name", "string", {"size": 100}) + t.Column("alive", "boolean", {"null": true}) + t.Column("birth_date", "timestamp", {"null": true}) + t.Column("bio", "text", {"null": true}) + t.Column("price", "numeric", {"null": true, "default": "1.00"}) + t.Column("email", "string", {"default": "foo@example.com", "size": 50}) + t.DisableTimestamps() +} diff --git a/fix/fixtures/auto_timestamps_off/patched/0004_already_patched.fizz b/fix/fixtures/auto_timestamps_off/patched/0004_already_patched.fizz new file mode 100644 index 00000000..3eddb3d8 --- /dev/null +++ b/fix/fixtures/auto_timestamps_off/patched/0004_already_patched.fizz @@ -0,0 +1,11 @@ +create_table("users_4") { + t.Column("id", "int", {primary: true}) + t.Column("name", "string", {}) + t.Column("user_name", "string", {"size": 100}) + t.Column("alive", "boolean", {"null": true}) + t.Column("birth_date", "timestamp", {"null": true}) + t.Column("bio", "text", {"null": true}) + t.Column("price", "numeric", {"null": true, "default": "1.00"}) + t.Column("email", "string", {"default": "foo@example.com", "size": 50}) + t.Timestamps() +} diff --git a/fix/fixtures/auto_timestamps_off/raw/0001_nominal.fizz b/fix/fixtures/auto_timestamps_off/raw/0001_nominal.fizz new file mode 100644 index 00000000..d0b7b06d --- /dev/null +++ b/fix/fixtures/auto_timestamps_off/raw/0001_nominal.fizz @@ -0,0 +1,10 @@ +create_table("users") { + t.Column("id", "int", {primary: true}) + t.Column("name", "string", {}) + t.Column("user_name", "string", {"size": 100}) + t.Column("alive", "boolean", {"null": true}) + t.Column("birth_date", "timestamp", {"null": true}) + t.Column("bio", "text", {"null": true}) + t.Column("price", "numeric", {"null": true, "default": "1.00"}) + t.Column("email", "string", {"default": "foo@example.com", "size": 50}) +} diff --git a/fix/fixtures/auto_timestamps_off/raw/0002_with_disabled_option.fizz b/fix/fixtures/auto_timestamps_off/raw/0002_with_disabled_option.fizz new file mode 100644 index 00000000..1b456174 --- /dev/null +++ b/fix/fixtures/auto_timestamps_off/raw/0002_with_disabled_option.fizz @@ -0,0 +1,10 @@ +create_table("users_2", {"timestamps": false}) { + t.Column("id", "int", {primary: true}) + t.Column("name", "string", {}) + t.Column("user_name", "string", {"size": 100}) + t.Column("alive", "boolean", {"null": true}) + t.Column("birth_date", "timestamp", {"null": true}) + t.Column("bio", "text", {"null": true}) + t.Column("price", "numeric", {"null": true, "default": "1.00"}) + t.Column("email", "string", {"default": "foo@example.com", "size": 50}) +} diff --git a/fix/fixtures/auto_timestamps_off/raw/0003_with_disable_timestamps.fizz b/fix/fixtures/auto_timestamps_off/raw/0003_with_disable_timestamps.fizz new file mode 100644 index 00000000..15602cbd --- /dev/null +++ b/fix/fixtures/auto_timestamps_off/raw/0003_with_disable_timestamps.fizz @@ -0,0 +1,11 @@ +create_table("users_3") { + t.Column("id", "int", {primary: true}) + t.Column("name", "string", {}) + t.Column("user_name", "string", {"size": 100}) + t.Column("alive", "boolean", {"null": true}) + t.Column("birth_date", "timestamp", {"null": true}) + t.Column("bio", "text", {"null": true}) + t.Column("price", "numeric", {"null": true, "default": "1.00"}) + t.Column("email", "string", {"default": "foo@example.com", "size": 50}) + t.DisableTimestamps() +} diff --git a/fix/fixtures/auto_timestamps_off/raw/0004_already_patched.fizz b/fix/fixtures/auto_timestamps_off/raw/0004_already_patched.fizz new file mode 100644 index 00000000..3eddb3d8 --- /dev/null +++ b/fix/fixtures/auto_timestamps_off/raw/0004_already_patched.fizz @@ -0,0 +1,11 @@ +create_table("users_4") { + t.Column("id", "int", {primary: true}) + t.Column("name", "string", {}) + t.Column("user_name", "string", {"size": 100}) + t.Column("alive", "boolean", {"null": true}) + t.Column("birth_date", "timestamp", {"null": true}) + t.Column("bio", "text", {"null": true}) + t.Column("price", "numeric", {"null": true, "default": "1.00"}) + t.Column("email", "string", {"default": "foo@example.com", "size": 50}) + t.Timestamps() +} diff --git a/soda/cmd/fix.go b/soda/cmd/fix.go index 6ce1b78f..75feea23 100644 --- a/soda/cmd/fix.go +++ b/soda/cmd/fix.go @@ -19,38 +19,52 @@ var fixCmd = &cobra.Command{ if info == nil { return nil } - ext := strings.ToLower(filepath.Ext(path)) - if ext != ".fizz" { - return nil - } + return fixFizz(path) + }) + }, +} - b, err := ioutil.ReadFile(path) - if err != nil { - return err - } +func fixFizz(path string) error { + ext := strings.ToLower(filepath.Ext(path)) + if ext != ".fizz" { + return nil + } - content := string(b) + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } - fixed, err := fix.Anko(content) - if err != nil { - return err - } - if strings.TrimSpace(fixed) != strings.TrimSpace(content) { - f, err := os.Create(path) - if err != nil { - return err - } - if _, err := f.WriteString(fixed); err != nil { - return err - } - if err := f.Close(); err != nil { - return err - } - } + content := string(b) - return nil - }) - }, + // Old anko format + fixed, err := fix.Anko(content) + if err != nil { + return err + } + if strings.TrimSpace(fixed) != strings.TrimSpace(content) { + content = fixed + } + + // Rewrite migrations to use t.Timestamps() if necessary + fixed, err = fix.AutoTimestampsOff(content) + if err != nil { + return err + } + + if strings.TrimSpace(fixed) != strings.TrimSpace(content) { + f, err := os.Create(path) + if err != nil { + return err + } + if _, err := f.WriteString(fixed); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + } + return nil } func init() {