Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
ashwini-desai committed Jul 6, 2020
2 parents 87990f7 + a6dc2bd commit 89dc8f8
Show file tree
Hide file tree
Showing 41 changed files with 600 additions and 303 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"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 Medly

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
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] [SQLFILES]...
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 relative to
base path (-b) 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)
}
}
91 changes: 91 additions & 0 deletions cli/src/main/kotlin/norm/cli/NormCli.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package norm.cli

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.arguments.unique
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 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()

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 file list is explicitly provided, it is relative to basePath
(inputFilesAsOpts + sqlFiles).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 89dc8f8

Please sign in to comment.