Skip to content

Commit

Permalink
refactoring - cleaner package structure and command line interface (#39)
Browse files Browse the repository at this point in the history
* refactoring - cleaner package structure and command line interface

* update documentation

* update norm-cli to norm-codegen

* upload the cli as artifact

* gradle autoupgrade
  • Loading branch information
kdabir authored Jul 6, 2020
1 parent 6871111 commit d00411d
Show file tree
Hide file tree
Showing 36 changed files with 549 additions and 301 deletions.
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

0 comments on commit d00411d

Please sign in to comment.