new tests
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ struct DBSynApp: App {
|
||||
}
|
||||
}
|
||||
.environmentObject(model)
|
||||
.sheet(isPresented: $showingMigrationGenerator) { LaravelMigrationView(model: model) }
|
||||
.sheet(isPresented: $showingMigrationGenerator) { LaravelMigrationView(model: model, onClose: { showingMigrationGenerator = false }) }
|
||||
.frame(minWidth: 800, minHeight: 520)
|
||||
}
|
||||
.defaultSize(width: 1200, height: 800)
|
||||
|
||||
@@ -19,10 +19,15 @@ enum LaravelMigrationGenerator {
|
||||
}
|
||||
guard depth == 0 else { throw NSError(domain: "DBSyn.Migration", code: 2, userInfo: [NSLocalizedDescriptionKey: "Unbalanced CREATE TABLE parentheses."]) }
|
||||
let body = String(ddl[start..<end]); var lines: [String] = []
|
||||
for definition in splitColumns(body) {
|
||||
let definitions = splitColumns(body)
|
||||
let primaryKeys = definitions.filter { $0.trimmingCharacters(in: .whitespacesAndNewlines).uppercased().hasPrefix("PRIMARY KEY") }
|
||||
.flatMap { backtickNames($0) }
|
||||
let autoPrimary = definitions.contains { $0.uppercased().contains("AUTO_INCREMENT") } && primaryKeys.count == 1
|
||||
for definition in definitions {
|
||||
let item = definition.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let upper = item.uppercased()
|
||||
if upper.hasPrefix("PRIMARY KEY") {
|
||||
if autoPrimary { continue }
|
||||
let columns = backtickNames(item)
|
||||
if columns.count == 1 {
|
||||
lines.append(" $table->primary('\(columns[0])');")
|
||||
@@ -71,6 +76,10 @@ enum LaravelMigrationGenerator {
|
||||
var args = "'\(col)'"
|
||||
if method == "string" || method == "char", let range = type.range(of: #"\(\s*\d+\s*\)"#, options: .regularExpression) { args += ", " + type[range].filter(\.isNumber) }
|
||||
if method == "decimal", let range = type.range(of: #"\(\s*\d+\s*,\s*\d+\s*\)"#, options: .regularExpression) { args += ", " + type[range].dropFirst().dropLast() }
|
||||
if upper.contains("AUTO_INCREMENT") && primaryKeys == [col] && method == "bigInteger" {
|
||||
lines.append(" $table->id('\(col)');")
|
||||
continue
|
||||
}
|
||||
var expr = " $table->\(method)(\(args))"
|
||||
if upper.contains("UNSIGNED") { expr += "->unsigned()" }
|
||||
if !upper.contains("NOT NULL") { expr += "->nullable()" }
|
||||
@@ -126,6 +135,9 @@ struct LaravelMigrationView: View {
|
||||
@State private var tableName = ""
|
||||
@State private var chosenDatabase = ""
|
||||
@State private var isLoading = false
|
||||
@State private var tableSearch = ""
|
||||
@State private var statusText = ""
|
||||
var onClose: () -> Void = {}
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
HStack {
|
||||
@@ -135,6 +147,8 @@ struct LaravelMigrationView: View {
|
||||
Text("Convert a MySQL table or CREATE TABLE SQL into a Laravel migration.").foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Button(action: onClose) { Image(systemName: "xmark.circle.fill").font(.title2).foregroundStyle(.secondary) }
|
||||
.buttonStyle(.plain).help("Close Migration Generator")
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack(spacing: 12) {
|
||||
@@ -158,13 +172,16 @@ struct LaravelMigrationView: View {
|
||||
TextField("Enter a table name", text: $tableName)
|
||||
.frame(maxWidth: 330)
|
||||
if !availableTables.isEmpty {
|
||||
Picker("Choose table", selection: $tableName) {
|
||||
Text("Choose table…").tag("")
|
||||
ForEach(availableTables, id: \.self) { name in
|
||||
Text(name).tag(name)
|
||||
Menu {
|
||||
TextField("Search tables", text: $tableSearch)
|
||||
Divider()
|
||||
ForEach(filteredTables.prefix(100), id: \.self) { name in
|
||||
Button(name) { tableName = name }
|
||||
}
|
||||
if filteredTables.isEmpty { Text("No matching tables") }
|
||||
} label: {
|
||||
Label("Browse Tables (\(availableTables.count))", systemImage: "magnifyingglass")
|
||||
}
|
||||
.labelsHidden()
|
||||
.frame(maxWidth: 230)
|
||||
}
|
||||
Button(isLoading ? "Loading…" : "Load Table") { loadTable() }
|
||||
@@ -188,10 +205,12 @@ struct LaravelMigrationView: View {
|
||||
}
|
||||
}
|
||||
if !errorText.isEmpty { Text(errorText).foregroundStyle(.red).font(.callout) }
|
||||
if !statusText.isEmpty { Text(statusText).foregroundStyle(.green).font(.callout) }
|
||||
HStack {
|
||||
Text("Review indexes, foreign keys, defaults and unsupported MySQL types before running a migration.").font(.caption).foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Button("Generate") { generate() }.buttonStyle(.borderedProminent)
|
||||
Button("Generate / Refresh") { generate() }.buttonStyle(.borderedProminent)
|
||||
.disabled(sourceSQL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
||||
Button("Copy PHP") { NSPasteboard.general.clearContents(); NSPasteboard.general.setString(generated, forType: .string) }.disabled(generated.isEmpty)
|
||||
Button("Save Migration…") { save() }.disabled(generated.isEmpty)
|
||||
}
|
||||
@@ -203,7 +222,8 @@ struct LaravelMigrationView: View {
|
||||
chosenDatabase = model.completionDatabase
|
||||
}
|
||||
}
|
||||
.onChange(of: chosenDatabase) { _, _ in tableName = "" }
|
||||
.onChange(of: chosenDatabase) { _, _ in tableName = ""; tableSearch = "" }
|
||||
.onChange(of: sourceSQL) { _, _ in statusText = "Source changed — select Generate / Refresh to update PHP." }
|
||||
}
|
||||
private var databaseOptions: [String] {
|
||||
Array(Set(model.databases + model.schemaTables.keys + [model.completionDatabase].filter { !$0.isEmpty })).sorted()
|
||||
@@ -211,9 +231,19 @@ struct LaravelMigrationView: View {
|
||||
private var availableTables: [String] {
|
||||
model.schemaTables[chosenDatabase] ?? []
|
||||
}
|
||||
private var filteredTables: [String] {
|
||||
let query = tableSearch.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return query.isEmpty ? availableTables : availableTables.filter { $0.localizedCaseInsensitiveContains(query) }
|
||||
}
|
||||
private func generate() {
|
||||
do { generated = try LaravelMigrationGenerator.generate(sourceSQL); errorText = "" }
|
||||
catch { errorText = error.localizedDescription }
|
||||
do {
|
||||
generated = try LaravelMigrationGenerator.generate(sourceSQL)
|
||||
errorText = ""
|
||||
statusText = "Migration generated — review the PHP, then Copy or Save."
|
||||
} catch {
|
||||
errorText = error.localizedDescription
|
||||
statusText = ""
|
||||
}
|
||||
}
|
||||
private func openFile() {
|
||||
let panel = NSOpenPanel(); panel.allowedContentTypes = [UTType(filenameExtension: "sql") ?? .plainText, .plainText]; panel.allowsMultipleSelection = false
|
||||
@@ -248,7 +278,7 @@ struct LaravelMigrationView: View {
|
||||
let name = tableName.isEmpty ? "table" : tableName.lowercased()
|
||||
panel.nameFieldStringValue = "\(prefix)_\(name)_table.php"
|
||||
if panel.runModal() == .OK, let url = panel.url {
|
||||
do { try generated.write(to: url, atomically: true, encoding: .utf8); errorText = "" }
|
||||
do { try generated.write(to: url, atomically: true, encoding: .utf8); errorText = ""; statusText = "Saved: \(url.lastPathComponent)" }
|
||||
catch { errorText = error.localizedDescription }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user