Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactoring - cleaner package structure and command line interface #39

Merged
merged 6 commits into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ insert_final_newline = true
indent_size = 4
indent_style = space

[*.{sql}]
indent_size = 4
indent_style = space

[*.md]
trim_trailing_whitespace = false

Expand Down
38 changes: 28 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
name: Build
name: Build the Distribution

on: [push]
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- name: Set up JDK
uses: actions/[email protected]
with:
java-version: 11
- name: Build with Gradle
run: ./gradlew build
- name: Checkout code
uses: actions/checkout@v2

- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build with Gradle
run: gradle clean build

- name: Dist
run: gradle cli:distZip

- name: Upload binary
uses: actions/upload-artifact@v2
with:
name: norm-codegen
path: cli/build/distributions/norm-codegen.zip
27 changes: 27 additions & 0 deletions .github/workflows/gradle-upgrade.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## runs gradle wrapper task and creates a PR

name: Upgrade Gradle

on:
push:
branches: [ master ]

schedule:
- cron: 0 2 * * 1

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Run a one-line script
run: |
LATEST_VER=$(curl https://api.github.com/repos/gradle/gradle/releases/latest | jq -r .name)
./gradlew wrapper --gradle-version ${LATEST_VER} --distribution-type all
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
commit-message: "[gradle-updater] updating gradle wrapper to latest version"
title: "Upgrade Gradle"
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,39 @@ Hence, Norm has two packages:
println(res.address)
}
```



## Command Line Interface

Norm CLI can be used to generate Kotlin files corresponding to SQL files.

we can provide multiple of files using `-f some/path/a.sql -f some/path/b.sql`. This will generate Kotlin files
at `some.path.A.kt` & `some.path.A.kt`. If we want to exclude `some` from package name then we must use `-b` option
with the base dir `-f some/path/a.sql -f some/path/b.sql -b some/`. Now the kotlin files will be generated in package
`path.A.kt` & `path.A.kt` inside the `output-dir`.

If option `--in-dir` is used, all the `*.sql` files will be used for code generation.

```
$ norm-codegen --help

Usage: norm-codegen [OPTIONS]

Generates Kotlin Source files for given SQL files using the Postgres
database connection

Options:
-j, --jdbc-url TEXT JDBC connection URL (can use env var PG_JDBC_URL)
-u, --username TEXT Username (can use env var PG_USERNAME)
-p, --password TEXT Password (can use env var PG_PASSWORD)
-b, --base-path DIRECTORY relative path from this dir will be used to infer
package name
-f, --file FILE [Multiple] SQL files, the file path will be used
to infer package name
-d, --in-dir DIRECTORY Dir containing .sql files, relative path from
this dir will be used to infer package name
-o, --out-dir DIRECTORY Output dir where source should be generated
-h, --help Show this message and exit

```
25 changes: 23 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ plugins {
subprojects {
apply plugin: 'org.jetbrains.kotlin.jvm'

group = 'com.medly.norm'

repositories {
jcenter()
}
Expand All @@ -16,8 +18,8 @@ subprojects {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
testImplementation 'io.kotlintest:kotlintest-runner-junit5:3.4.2'
testCompile 'org.testcontainers:postgresql:1.14.3'
testCompile 'postgresql:postgresql:9.1-901-1.jdbc4'
testImplementation 'org.testcontainers:postgresql:1.14.3'
testImplementation 'postgresql:postgresql:9.1-901-1.jdbc4'
}

test {
Expand All @@ -28,6 +30,12 @@ subprojects {
exceptionFormat "full"
}
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = "1.8"
}
}
}

project(":codegen") {
Expand All @@ -42,3 +50,16 @@ project(":codegen") {
testImplementation("io.kotlintest:kotlintest-runner-junit5:3.3.0")
}
}

project(":cli") {
apply plugin: 'application'

applicationName = 'norm-codegen'
mainClassName = "norm.cli.NormCliKt"

dependencies {
implementation project(":codegen")
implementation 'org.postgresql:postgresql:42.2.14'
implementation "com.github.ajalt:clikt:2.7.1"
}
}
25 changes: 25 additions & 0 deletions cli/src/main/kotlin/norm/api/NormApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package norm.api

import norm.analyzer.SqlAnalyzer
import norm.codegen.CodeGenerator
import java.sql.Connection

/**
* Initialize with a given postgres connection
*
* Generates code of classes against given SQL Query
*/
class NormApi(
connection: Connection
) {
private val sqlAnalyzer = SqlAnalyzer(connection)
private val codeGenerator = CodeGenerator()

/**
* Generates code of classes against given SQL Query
*/
fun generate(query: String, packageName: String, baseName: String): String {
val sqlModel = sqlAnalyzer.sqlModel(query)
return codeGenerator.generate(sqlModel, packageName, baseName)
}
}
83 changes: 83 additions & 0 deletions cli/src/main/kotlin/norm/cli/NormCli.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package norm.cli

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.types.file
import norm.api.NormApi
import norm.fs.IO
import norm.fs.globSearch
import norm.util.withPGConnection
import java.io.File
import kotlin.system.exitProcess


/**
* Entry point - The main function
*/
fun main(args: Array<String>) = NormCli().main(args)


/**
* Implementation of CLI using Norm API
*
* Can use env variable to pass in sensitive information
*/
class NormCli : CliktCommand( // command name is inferred as norm-cli
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 inputFiles by option("-f", "--file", help = "[Multiple] SQL files, the file path will be used to infer package name")
.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 outDir by option("-o", "--out-dir", help = "Output dir where source should be generated")
.file(canBeFile = false, canBeDir = true, mustExist = true)
.required()


override fun run() {
try {
withPGConnection(jdbcUrl, username, password) { connection ->
val normApi = NormApi(connection)

// If dir is provided, relativize to itself
inputDir?.let { dir ->
globSearch(dir, "**.sql").forEach { sqlFile ->
IO(sqlFile, dir, outDir).process(normApi::generate)
}
}

// If dir is provided, relativize to basePath
inputFiles.forEach { sqlFile ->
IO(sqlFile, basePath, outDir).process(normApi::generate)
}
}
exitProcess(0)
} catch (e: Exception) {
e.printStackTrace()
exitProcess(1)
}
}
}


25 changes: 25 additions & 0 deletions cli/src/main/kotlin/norm/fs/IO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package norm.fs

import norm.util.toTitleCase
import java.io.File

class IO(
private val sqlFile: File,
baseDir: File,
outDir: File
) {
private val sqlFileRelativeToSource = sqlFile.relativeTo(baseDir)
private val nameWithoutExtension = sqlFileRelativeToSource.nameWithoutExtension
private val parentPath = sqlFileRelativeToSource.parent
private val packageName = parentPath.replace(File.separator, ".")
private val baseName = toTitleCase(nameWithoutExtension)
private val outFileParentDir = File(outDir, parentPath)
private val outputFile = File(outFileParentDir, "$baseName.kt")

fun process(block: (query: String, packageName: String, baseName: String) -> String) {
outFileParentDir.mkdirs()
println("will write to $outputFile")
outputFile.writeText(block(sqlFile.readText(), packageName, baseName))
}
}

18 changes: 18 additions & 0 deletions cli/src/main/kotlin/norm/fs/Search.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package norm.fs

import java.io.File
import java.nio.file.FileSystems

/**
* Searches within a directory using glob style patterns
*
* example "**.txt"
*
* @param root must be Root Directory in which search will be performed
* @param pattern must be valid Glob Pattern
*
*/
fun globSearch(root: File, pattern: String): Sequence<File> {
val pathMatcher = FileSystems.getDefault().getPathMatcher("glob:$pattern")!!
return root.walkTopDown().filter { file -> pathMatcher.matches(file.toPath()) }
}
14 changes: 14 additions & 0 deletions cli/src/main/kotlin/norm/util/PG.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package norm.util

import org.postgresql.ds.PGSimpleDataSource
import java.sql.Connection

/**
* provides connection to the callback and closes it when done, this way code block does not need to manage connection
*/
fun withPGConnection(url: String, username: String, password: String, fn: (Connection) -> Unit) =
PGSimpleDataSource().also {
it.setUrl(url) // url is not a property
it.user = username
it.password = password
}.connection.use(fn)
Loading