using System; using System.IO; using System.Text.RegularExpressions; namespace Migrato.Core; /// /// Parses a "-- +migrato Up" / "-- +migrato Down" annotated SQL file into a /// public static partial class MigrationParser { private const string UpMarker = "-- +migrato Up"; private const string DownMarker = "-- +migrato Down"; /// /// Parses the migration file at /// /// The file name or contents do not follow Migrato's conventions public static Migration Parse(string path) { var fileName = Path.GetFileName(path); var match = FileNameRegex().Match(fileName); if (!match.Success) { throw new MigrationFormatException( $"'{fileName}' is not a valid migration file name; expected '<14-digit version>_.sql'"); } var version = long.Parse(match.Groups["version"].Value); var name = match.Groups["name"].Value; var content = File.ReadAllText(path); var (upSql, downSql) = ParseSections(content, fileName); return new Migration(version) { Source = path, Name = name, UpSql = upSql, DownSql = downSql, }; } private static (string UpSql, string DownSql) ParseSections(string content, string fileName) { var lines = content.Replace("\r\n", "\n").Split('\n'); var upIndex = -1; var downIndex = -1; for (var i = 0; i < lines.Length; i++) { var trimmed = lines[i].Trim(); if (trimmed.Equals(UpMarker, StringComparison.OrdinalIgnoreCase)) { if (upIndex != -1) { throw new MigrationFormatException($"'{fileName}' has more than one '{UpMarker}' marker"); } upIndex = i; } else if (trimmed.Equals(DownMarker, StringComparison.OrdinalIgnoreCase)) { if (downIndex != -1) { throw new MigrationFormatException($"'{fileName}' has more than one '{DownMarker}' marker"); } downIndex = i; } } if (upIndex == -1) { throw new MigrationFormatException($"'{fileName}' is missing a '{UpMarker}' marker"); } if (downIndex == -1) { throw new MigrationFormatException($"'{fileName}' is missing a '{DownMarker}' marker"); } if (downIndex < upIndex) { throw new MigrationFormatException($"'{fileName}' has '{DownMarker}' before '{UpMarker}'"); } var upSql = string.Join('\n', lines[(upIndex + 1)..downIndex]).Trim(); var downSql = string.Join('\n', lines[(downIndex + 1)..]).Trim(); return (upSql, downSql); } [GeneratedRegex(@"^(?\d{14})_(?.+)\.sql$")] private static partial Regex FileNameRegex(); }