func parseMigrationContents(contents string) (Migration, Migration, error) {
	up := NewMigration()
	down := NewMigration()
	upDirectiveStart, upDirectiveEnd, hasDefinedUpBlock := getMatchPositions(contents, upRegExp)
	downDirectiveStart, downDirectiveEnd, hasDefinedDownBlock := getMatchPositions(contents, downRegExp)
	if !hasDefinedUpBlock {
		return up, down, fmt.Errorf("dbmate requires each migration to define an up bock with '-- migrate:up'")
	} else if statementsPrecedeMigrateBlocks(contents, upDirectiveStart, downDirectiveStart) {
		return up, down, fmt.Errorf("dbmate does not support statements defined outside of the '-- migrate:up' or '-- migrate:down' blocks")
	}
	upEnd := len(contents)
	downEnd := len(contents)
	if hasDefinedDownBlock && upDirectiveStart < downDirectiveStart {
		upEnd = downDirectiveStart
	} else if hasDefinedDownBlock && upDirectiveStart > downDirectiveStart {
		downEnd = upDirectiveStart
	} else {
		downEnd = -1
	}
	upDirective := substring(contents, upDirectiveStart, upDirectiveEnd)
	downDirective := substring(contents, downDirectiveStart, downDirectiveEnd)
	up.Options = parseMigrationOptions(upDirective)
	up.Contents = substring(contents, upDirectiveStart, upEnd)
	down.Options = parseMigrationOptions(downDirective)
	down.Contents = substring(contents, downDirectiveStart, downEnd)
	return up, down, nil
}
parseMigrationContents parses the string contents of a migration.
It will return two Migration objects, the first representing the "up"
block and the second representing the "down" block. This function
requires that at least an up block was defined and will otherwise
return an error.
package dbmate
import (
	"fmt"
	"os"
	"regexp"
	"strings"
)
type MigrationOptions interface {
	Transaction() bool
}
type migrationOptions map[string]string
func (m migrationOptions) Transaction() bool {
	return m["transaction"] != "false"
}
type Migration struct {
	Contents string
	Options  MigrationOptions
}
func NewMigration() Migration {
	return Migration{Contents: "", Options: make(migrationOptions)}
}
func parseMigration(path string) (Migration, Migration, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return NewMigration(), NewMigration(), err
	}
	up, down, err := parseMigrationContents(string(data))
	return up, down, err
}
var upRegExp = regexp.MustCompile(`(?m)^--\s*migrate:up(\s*$|\s+\S+)`)
var downRegExp = regexp.MustCompile(`(?m)^--\s*migrate:down(\s*$|\s+\S+)$`)
var emptyLineRegExp = regexp.MustCompile(`^\s*$`)
var commentLineRegExp = regexp.MustCompile(`^\s*--`)
var whitespaceRegExp = regexp.MustCompile(`\s+`)
var optionSeparatorRegExp = regexp.MustCompile(`:`)
var blockDirectiveRegExp = regexp.MustCompile(`^--\s*migrate:[up|down]]`)