Repository: ariga/atlas
Pull request #1423 (2023-01-25 12:43:31)

sql/migrate: support reading file directives
Initial work for #1414. 


Patch:
diff --git a/doc/website/docusaurus.config.js b/doc/website/docusaurus.config.js
index e7810e14..dcf3a0d6 100644
--- a/doc/website/docusaurus.config.js
+++ b/doc/website/docusaurus.config.js
@@ -127,8 +127,8 @@ module.exports = {
       `,
     },
     announcementBar: {
-      id: 'announcementBar-1', // Increment on change
-      content: `️🚀 Sign up for a user testing session and receive exclusive Atlas swag, register <a target="_blank" rel="noopener noreferrer" href="https://calendly.com/ariga-user-testing/atlas-user-testing">here!</a>`,
+      id: 'announcementBar-2', // Increment on change
+      content: `️🚀 Read the latest updates in <a target="_blank" rel="noopener noreferrer" href="https://atlasnewsletter.substack.com/">our newsletter!</a>`, 
       isCloseable: true,
     },
   },
diff --git a/sql/migrate/dir.go b/sql/migrate/dir.go
index ab4e1634..787ce788 100644
--- a/sql/migrate/dir.go
+++ b/sql/migrate/dir.go
@@ -179,6 +179,37 @@ func (f LocalFile) Bytes() []byte {
 	return f.b
 }
 
+// Directive returns the (global) file directives that match the provided name.
+// File directives are located at the top of the file and should not be associated with any
+// statement. Hence, double new lines are used to separate file directives from its content.
+func (f LocalFile) Directive(name string) (ds []string) {
+	var (
+		comments []string
+		content  = string(f.b)
+	)
+	for strings.HasPrefix(content, "#") || strings.HasPrefix(content, "--") {
+		idx := strings.IndexByte(content, '\n')
+		if idx == -1 {
+			// Comments-only file.
+			comments = append(comments, content)
+			break
+		}
+		comments = append(comments, strings.TrimSpace(content[:idx]))
+		content = content[idx+1:]
+	}
+	// File directives are separated by
+	// double newlines from file content.
+	if !strings.HasPrefix(content, "\n") {
+		return nil
+	}
+	for _, c := range comments {
+		if d, ok := directive(c, name); ok {
+			ds = append(ds, d)
+		}
+	}
+	return ds
+}
+
 // MemDir provides an in-memory Dir implementation.
 type MemDir struct {
 	files map[string]File


Test patch:
diff --git a/sql/migrate/dir_test.go b/sql/migrate/dir_test.go
index d46b1a41..7cb5de1c 100644
--- a/sql/migrate/dir_test.go
+++ b/sql/migrate/dir_test.go
@@ -215,3 +215,39 @@ func TestMemDir(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, hs1, hs2)
 }
+
+func TestLocalFile_Directive(t *testing.T) {
+	f := migrate.NewLocalFile("1.sql", []byte(`-- atlas:lint ignore
+alter table users drop column id;
+`))
+	require.Empty(t, f.Directive("lint"), "statement directives are ignored")
+
+	f = migrate.NewLocalFile("1.sql", []byte(`-- atlas:lint ignore
+
+alter table users drop column id;
+
+-- atlas:lint DS102
+alter table pets drop column id;
+`))
+	require.Equal(t, []string{"ignore"}, f.Directive("lint"), "single directive")
+
+	f = migrate.NewLocalFile("1.sql", []byte(`-- atlas:lint ignore
+-- atlas:txmode none
+
+alter table users drop column id;
+
+-- atlas:lint DS102
+alter table pets drop column id;
+`))
+	require.Equal(t, []string{"ignore"}, f.Directive("lint"), "first directive from two")
+	require.Equal(t, []string{"none"}, f.Directive("txmode"), "second directive from two")
+
+	f = migrate.NewLocalFile("1.sql", nil)
+	require.Empty(t, f.Directive("lint"))
+	f = migrate.NewLocalFile("1.sql", []byte("-- atlas:lint ignore"))
+	require.Empty(t, f.Directive("lint"))
+	f = migrate.NewLocalFile("1.sql", []byte("-- atlas:lint ignore\n"))
+	require.Empty(t, f.Directive("lint"))
+	f = migrate.NewLocalFile("1.sql", []byte("-- atlas:lint ignore\n\n"))
+	require.Equal(t, []string{"ignore"}, f.Directive("lint"), "double newline as directive separator")
+}