diff --git a/cli/build.gradle b/cli/build.gradle index 557d835..5a4d35b 100644 --- a/cli/build.gradle +++ b/cli/build.gradle @@ -11,5 +11,7 @@ mainClassName = "norm.cli.NormCliKt" dependencies { implementation project(":codegen") implementation "com.github.ajalt:clikt:2.8.0" + testImplementation "io.mockk:mockk:1.11.0" + implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '5.11.0.202103091610-r' } diff --git a/cli/src/main/kotlin/norm/cli/NormCli.kt b/cli/src/main/kotlin/norm/cli/NormCli.kt index aa485ce..fca1c91 100644 --- a/cli/src/main/kotlin/norm/cli/NormCli.kt +++ b/cli/src/main/kotlin/norm/cli/NormCli.kt @@ -10,6 +10,8 @@ import norm.api.NormApi import norm.fs.IO import norm.fs.globSearch import norm.util.withPgConnection +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.storage.file.FileRepositoryBuilder import java.io.File @@ -25,41 +27,68 @@ fun main(args: Array) = NormCli().main(args) * Can use env variable to pass in sensitive information */ class NormCli : CliktCommand( // command name is inferred as norm-cli - name = "norm-codegen", - help = """ + name = "norm-codegen", + help = """ Generates Kotlin Source files for given SQL files using the Postgres database connection """ ) { - private val jdbcUrl by option("-j", "--jdbc-url", help = "JDBC connection URL (can use env var PG_JDBC_URL)", envvar = "PG_JDBC_URL") - .default("jdbc:postgresql://localhost/postgres") - - private val username by option("-u", "--username", help = "Username (can use env var PG_USERNAME)", envvar = "PG_USERNAME") - .default("postgres") - - private val password by option("-p", "--password", help = "Password (can use env var PG_PASSWORD)", envvar = "PG_PASSWORD") - .default("") - - private val basePath by option("-b", "--base-path", help = " relative path from this dir will be used to infer package name") - .file(canBeFile = false, canBeDir = true, mustExist = true) - .default(File(".")) // Current working dir - - private val inputFilesAsOpts by option("-f", "--file", help = "[Multiple] SQL files, the file path relative to base path (-b) will be used to infer package name") - .file(canBeFile = true, canBeDir = false, mustExist = true) - .multiple() - .unique() + private val jdbcUrl by option( + "-j", + "--jdbc-url", + help = "JDBC connection URL (can use env var PG_JDBC_URL)", + envvar = "PG_JDBC_URL" + ) + .default("jdbc:postgresql://localhost/postgres") + + private val username by option( + "-u", + "--username", + help = "Username (can use env var PG_USERNAME)", + envvar = "PG_USERNAME" + ) + .default("postgres") + + private val password by option( + "-p", + "--password", + help = "Password (can use env var PG_PASSWORD)", + envvar = "PG_PASSWORD" + ) + .default("") + + private val basePath by option( + "-b", + "--base-path", + help = " relative path from this dir will be used to infer package name" + ) + .file(canBeFile = false, canBeDir = true, mustExist = true) + .default(File(".")) // Current working dir + + private val inputFilesAsOpts by option( + "-f", + "--file", + help = "[Multiple] SQL files, the file path relative to base path (-b) will be used to infer package name" + ) + .file(canBeFile = true, canBeDir = false, mustExist = true) + .multiple() + .unique() private val sqlFiles by argument() // give meaningful name for CLI help message - .file(canBeFile = true, canBeDir = false, mustExist = true) - .multiple() - .unique() + .file(canBeFile = true, canBeDir = false, mustExist = true) + .multiple() + .unique() - private val inputDir by option("-d", "--in-dir", help = "Dir containing .sql files, relative path from this dir will be used to infer package name") - .file(canBeFile = false, canBeDir = true, mustExist = true) + private val inputDir by option( + "-d", + "--in-dir", + help = "Dir containing .sql files, relative path from this dir will be used to infer package name" + ) + .file(canBeFile = false, canBeDir = true, mustExist = true) private val outDir by option("-o", "--out-dir", help = "Output dir where source should be generated") - .file(canBeFile = false, canBeDir = true, mustExist = true) - .required() + .file(canBeFile = false, canBeDir = true, mustExist = true) + .required() override fun run() = withPgConnection(jdbcUrl, username, password) { connection -> @@ -67,7 +96,9 @@ class NormCli : CliktCommand( // command name is inferred as norm-cli // If dir is provided, relativize to itself inputDir?.let { dir -> - globSearch(dir, "**.sql").forEach { sqlFile -> + val fileList = globSearch(dir, "**.sql") + val modifiedFiles = modifiedFilesFromGit(dir,fileList) + modifiedFiles.forEach { sqlFile -> IO(sqlFile, dir, outDir).process(normApi::generate) } } @@ -77,6 +108,25 @@ class NormCli : CliktCommand( // command name is inferred as norm-cli IO(sqlFile, basePath, outDir).process(normApi::generate) } } + + private fun modifiedFilesFromGit(directory: File, fileList: Sequence): List { + + val repo = FileRepositoryBuilder() + .setGitDir(File(directory.parent + "/.git")) + .setMustExist(false) + .build() + val git = Git(repo) + return when { + repo.objectDatabase.exists() -> { + return when { + git.status().call().untracked.isNotEmpty() -> git.status().call().untracked + git.status().call().modified.isNotEmpty() -> git.status().call().modified + else -> git.diff().call().map { diffEntry -> diffEntry.newPath } + }.map { File(directory.parent+"/"+it) }.filter { it.name.endsWith("sql") } + } + else -> fileList.toList() + } + } } diff --git a/cli/src/test/kotlin/norm/cli/NormCliTest.kt b/cli/src/test/kotlin/norm/cli/NormCliTest.kt index e5c98a0..3f1daa0 100644 --- a/cli/src/test/kotlin/norm/cli/NormCliTest.kt +++ b/cli/src/test/kotlin/norm/cli/NormCliTest.kt @@ -10,8 +10,14 @@ import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.string.contain import io.kotest.matchers.string.startWith +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkConstructor import norm.test.utils.PgContainer import norm.test.utils.toArgs +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.internal.storage.file.FileRepository +import org.eclipse.jgit.storage.file.FileRepositoryBuilder import java.io.File class NormCliTest : StringSpec() { @@ -74,5 +80,21 @@ class NormCliTest : StringSpec() { exception.message should startWith("Invalid value for \"-f\":") exception.message should contain("is a directory") } + "Should generate kotlin file for untracked file in git"{ + + val repo = mockk(relaxed = true) + mockkConstructor(FileRepositoryBuilder::class) + mockkConstructor(Git::class) + every { anyConstructed().setGitDir(any()).setMustExist(any()).build() } returns repo + every { repo.objectDatabase.exists() } returns true + every { anyConstructed().status().call().untracked } returns setOf("sql/employees/add-new-employee.sql") + every { anyConstructed().status().call().modified } returns emptySet() + every { anyConstructed().diff().call() } returns emptyList() + + val args = toArgs("-d src/test/resources/gitrepository/sql -b src/test/resources/gitrepository/sql -o $outputDir ${pgStr()}") + NormCli().parse(args) + File("$outputDir/employees/AddNewEmployee.kt").exists() shouldBe true + + } } } diff --git a/cli/src/test/resources/gitrepository/sql/employees/add-new-employee.sql b/cli/src/test/resources/gitrepository/sql/employees/add-new-employee.sql new file mode 100644 index 0000000..053cfa6 --- /dev/null +++ b/cli/src/test/resources/gitrepository/sql/employees/add-new-employee.sql @@ -0,0 +1,2 @@ +INSERT INTO employees(first_name, last_name) +VALUES (:firstName, :lastName)