Skip to content

Commit

Permalink
Write to stdout about what we did to each file. Add more tests (#10)
Browse files Browse the repository at this point in the history
* Write to stdout about what we did to each file. Add more tests

* Nits to safeCopyTo
  • Loading branch information
felipecsl authored Feb 19, 2019
1 parent eef3c65 commit 8c90d64
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
import io.ktor.application.ApplicationCall
import kales.actionpack.ApplicationController
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.OutputStreamWriter
import java.nio.charset.StandardCharsets

/** Generates a controller class */
class GenerateControllerCommandRunner(
Expand All @@ -19,7 +22,7 @@ class GenerateControllerCommandRunner(
File(workingDirectory, listOf("src", "main", "kotlin").joinToString(File.separator))

fun run() {
val appDirectory = findKotlinDirectory()
val appDirectory = findAppDirectory()
?: throw UsageError("Unable to find the `app` sources directory")
val controllerName = when {
name.endsWith("Controller.kt") -> name.replace(".kt", "")
Expand All @@ -32,14 +35,26 @@ class GenerateControllerCommandRunner(
Make sure your sources directory structure follows the default
"src/main/kotlin/your/package/name" structure
""".trimIndent())
writeControllerClassFile(kotlinDir, controllerName, packageName)
val controllersDir = File(appDirectory, "controllers")
writeControllerClassFile(controllersDir, controllerName, packageName)
}

private fun writeControllerClassFile(
controllersDir: File,
controllerName: String,
appPackageName: String
) {
val file = buildFileSpec(controllerName, appPackageName)
val outputPath = controllersDir.toPath().resolve("$controllerName.kt")
ByteArrayOutputStream().use { baos ->
OutputStreamWriter(baos, StandardCharsets.UTF_8).use { writer ->
file.writeTo(writer)
}
outputPath.toFile().safeWriteText(baos.toString())
}
}

private fun buildFileSpec(controllerName: String, appPackageName: String): FileSpec {
val controllerTypeSpec = TypeSpec.classBuilder(controllerName)
.primaryConstructor(FunSpec.constructorBuilder()
.addParameter("call", ApplicationCall::class)
Expand All @@ -48,10 +63,9 @@ class GenerateControllerCommandRunner(
.addSuperclassConstructorParameter("call")
.addControllerActions()
.build()
val file = FileSpec.builder("$appPackageName.app.controllers", controllerName)
return FileSpec.builder("$appPackageName.app.controllers", controllerName)
.addType(controllerTypeSpec)
.build()
file.writeTo(controllersDir)
}

private fun TypeSpec.Builder.addControllerActions(): TypeSpec.Builder {
Expand All @@ -68,7 +82,7 @@ class GenerateControllerCommandRunner(
}

/** Returns a File pointing to the application app/`type` directory or null if none found */
private fun findKotlinDirectory(): File? {
private fun findAppDirectory(): File? {
return kotlinDir.childDirectories()
.mapNotNull(this::recursivelyFindAppDirectory)
.firstOrNull()
Expand Down
41 changes: 25 additions & 16 deletions kales-cli/src/main/kotlin/kales/cli/NewCommandRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kales.cli
import com.github.ajalt.clikt.core.UsageError
import java.io.File
import java.nio.file.Files
import java.nio.file.Files.exists
import java.nio.file.Path
import java.nio.file.attribute.PosixFilePermissions

Expand All @@ -12,10 +13,8 @@ class NewCommandRunner(
private val appName: String
) {
fun run() {
if (!appDir.exists() && !appDir.mkdirs()) {
throw UsageError("Failed to create directory ${appDir.absolutePath}")
}
File(appDir, "build.gradle").writeText(buildFileContents())
checkTargetDirectory()
File(appDir, "build.gradle").safeWriteText(buildFileContents())
val srcDirRelativePath = (setOf("src", "main", "kotlin") + appName.split("."))
.joinToString(File.separator)
val appSourceDir = File(File(appDir, srcDirRelativePath), "app")
Expand All @@ -26,27 +25,37 @@ class NewCommandRunner(
val gradleWrapperDir = setOf("gradle", "wrapper").joinToString(File.separator)
File(appDir, gradleWrapperDir).mkdirs()
File(File(appDir, gradleWrapperDir), "gradle-wrapper.properties")
.writeText(GRADLE_WRAPPER_FILE_CONTENTS)
.safeWriteText(GRADLE_WRAPPER_FILE_CONTENTS)
copyResource("gradle-wrapper.bin", File(File(appDir, gradleWrapperDir), "gradle-wrapper.jar"))
File(appDir, "gradlew").also { gradlewFile ->
makeExecutable(gradlewFile.toPath())
gradlewFile.toPath().makeExecutable()
copyResource("gradlew", gradlewFile)
}
println("""
New Kales project successfully initialized at '${appDir.absoluteFile.absolutePath}'.
Happy coding!
""".trimIndent())
}

private fun copyResource(resourceName: String, destination: File) {
val classLoader = New::class.java.classLoader
classLoader.getResourceAsStream(resourceName).use { input ->
destination.outputStream().use { output ->
input.copyTo(output)
}
private fun checkTargetDirectory() {
if (!appDir.exists() && !appDir.mkdirs()) {
throw UsageError("Failed to create directory ${appDir.absolutePath}")
}
}

private fun makeExecutable(file: Path) {
val ownerWritable = PosixFilePermissions.fromString("rwxr--r--")
val permissions = PosixFilePermissions.asFileAttribute(ownerWritable)
Files.createFile(file, permissions)
private fun copyResource(resourceName: String, destination: File) {
val inputStream = javaClass.classLoader.getResourceAsStream(resourceName)
// If the file is zero bytes we'll just consider it non-existing
inputStream.safeCopyTo(destination)
}

private fun Path.makeExecutable() {
if (!exists(this)) {
val ownerWritable = PosixFilePermissions.fromString("rwxr--r--")
val permissions = PosixFilePermissions.asFileAttribute(ownerWritable)
Files.createFile(this, permissions)
}
}

private fun buildFileContents() = """
Expand Down
63 changes: 62 additions & 1 deletion kales-cli/src/main/kotlin/kales/cli/Util.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,69 @@
package kales.cli

import com.github.ajalt.clikt.core.UsageError
import java.io.File
import java.io.InputStream
import java.nio.charset.Charset

/** Like list() but returns empty list instead of null */
fun File.safeListFiles(): List<File> {
return listFiles()?.asList() ?: emptyList()
}
}

/** Print messages about file changes when writing text to it */
fun File.safeWriteText(text: String, charset: Charset = Charsets.UTF_8) {
if (exists()) {
if (readText(charset) != text) {
printConflict()
// TODO add the option for the user to abort, overwrite or merge with current file
throw UsageError("Failed to write file since it already exists with different contents")
} else {
// Files are identical
printIdentical()
}
} else {
printCreated()
writeText(text, charset)
}
}

fun File.printCreated() {
printStatus("create")
}

fun File.printSkipped() {
printStatus("skip")
}

fun File.printIdentical() {
printStatus("identical")
}

fun File.printConflict() {
printStatus("conflict")
}

fun File.printStatus(status: String) {
val relativePath = relativePathToWorkingDir()
println(" $status $relativePath")
}

/** Copy streams and close at the end */
fun InputStream.safeCopyTo(destination: File): Long {
if (!destination.exists() || destination.length() == 0L) {
destination.printCreated()
} else {
destination.printSkipped()
}
use { input ->
destination.outputStream().use { output ->
return input.copyTo(output)
}
}
}

/** Returns a String with the path from this File relative to the current working dir (user.dir) */
fun File.relativePathToWorkingDir(): String {
val workingDir = File(System.getProperty("user.dir"))
return workingDir.toPath().relativize(absoluteFile.toPath()).toString()
}
95 changes: 95 additions & 0 deletions kales-cli/src/test/kotlin/kales/cli/CliTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package kales.cli

import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream

class CliTest {
@get:Rule val tempDir = TemporaryFolder()
private val originalOut = System.out
private val outContent = ByteArrayOutputStream()

@Before fun setUp() {
System.setOut(PrintStream(outContent))
}

@After fun tearDown() {
System.setOut(originalOut)
}

@Test fun `test kales new with absolute path`() {
New().parse(arrayOf(tempDir.root.absolutePath, "com.example"))
val stdOut = outContent.toString()
val buildGradleRelativePath = File(tempDir.root, "build.gradle").relativePathToWorkingDir()
val gradleWrapperPropsRelativePath = File(tempDir.root,
"gradle/wrapper/gradle-wrapper.properties").relativePathToWorkingDir()
val gradleWrapperJarRelativePath = File(tempDir.root,
"gradle/wrapper/gradle-wrapper.jar").relativePathToWorkingDir()
val gradlewRelativePath = File(tempDir.root, "gradlew").relativePathToWorkingDir()
assertThat(stdOut).isEqualTo("""
create $buildGradleRelativePath
create $gradleWrapperPropsRelativePath
create $gradleWrapperJarRelativePath
create $gradlewRelativePath
New Kales project successfully initialized at '${tempDir.root.absolutePath}'.
Happy coding!
""".trimIndent()
)
}

@Test fun `test kales new with relative path`() {
try {
val workingDir = File(System.getProperty("user.dir"))
New().parse(arrayOf("blah", "com.example"))
val stdOut = outContent.toString()
assertThat(stdOut).isEqualTo("""
create blah/build.gradle
create blah/gradle/wrapper/gradle-wrapper.properties
create blah/gradle/wrapper/gradle-wrapper.jar
create blah/gradlew
New Kales project successfully initialized at '${workingDir.absolutePath}/blah'.
Happy coding!
""".trimIndent())
} finally {
File("blah").deleteRecursively()
}
}

@Test fun `test identical files`() {
try {
val workingDir = File(System.getProperty("user.dir"))
New().parse(arrayOf("blah", "com.example"))
New().parse(arrayOf("blah", "com.example"))
val stdOut = outContent.toString()
assertThat(stdOut).isEqualTo("""
create blah/build.gradle
create blah/gradle/wrapper/gradle-wrapper.properties
create blah/gradle/wrapper/gradle-wrapper.jar
create blah/gradlew
New Kales project successfully initialized at '${workingDir.absolutePath}/blah'.
Happy coding!
identical blah/build.gradle
identical blah/gradle/wrapper/gradle-wrapper.properties
skip blah/gradle/wrapper/gradle-wrapper.jar
skip blah/gradlew
New Kales project successfully initialized at '${workingDir.absolutePath}/blah'.
Happy coding!
""".trimIndent())
} finally {
File("blah").deleteRecursively()
}
}
}
4 changes: 4 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh

cp kales /usr/local/bin
cp ../kales-cli/build/libs/kales-cli-0.0.1-SNAPSHOT-all.jar /usr/local/lib/kales.jar
Loading

0 comments on commit 8c90d64

Please sign in to comment.