Skip to content

Commit

Permalink
exitcode 1 on error in cli
Browse files Browse the repository at this point in the history
  • Loading branch information
jerrevanveluw committed Jan 3, 2025
1 parent bd43601 commit da7860f
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ sealed interface Input

data class FullDirPath(val path: String) : Input

data class FullFilePath(val directory: String, val fileName: FileName, val extension: FileExtension = Wirespec) :
Input {
data class FullFilePath(val directory: String, val fileName: FileName, val extension: FileExtension = Wirespec) : Input {
companion object {
fun parse(input: String): FullFilePath {
val list = input.split("/").let { it.dropLast(1) + it.last().split(".") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package community.flock.wirespec.plugin
import community.flock.wirespec.compiler.core.Value
import kotlin.jvm.JvmInline

interface Writer {
fun write(string: String)
}

@JvmInline
value class Output private constructor(override val value: String) : Value<String> {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package community.flock.wirespec.plugin.cli

import arrow.core.Either
import arrow.core.EitherNel
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.CliktError
import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.arguments.argument
Expand All @@ -11,6 +14,8 @@ import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.choice
import com.github.ajalt.clikt.parameters.types.enum
import community.flock.wirespec.compiler.core.emit.common.DEFAULT_GENERATED_PACKAGE_STRING
import community.flock.wirespec.compiler.core.emit.common.Emitted
import community.flock.wirespec.compiler.core.exceptions.WirespecException
import community.flock.wirespec.compiler.utils.Logger.Level
import community.flock.wirespec.compiler.utils.Logger.Level.DEBUG
import community.flock.wirespec.compiler.utils.Logger.Level.ERROR
Expand All @@ -27,6 +32,7 @@ import community.flock.wirespec.plugin.Language.Wirespec
import community.flock.wirespec.plugin.Operation
import community.flock.wirespec.plugin.Output
import community.flock.wirespec.plugin.PackageName
import community.flock.wirespec.plugin.cli.io.File

enum class Options(vararg val flags: String) {
InputDir("-d", "--input-dir"),
Expand All @@ -42,9 +48,10 @@ enum class Options(vararg val flags: String) {
class WirespecCli : NoOpCliktCommand(name = "wirespec") {
companion object {
fun provide(
compile: (CompilerArguments) -> Unit,
convert: (CompilerArguments) -> Unit,
): (Array<out String>) -> Unit = WirespecCli().subcommands(Compile(compile), Convert(convert))::main
compile: (CompilerArguments) -> List<EitherNel<WirespecException, Pair<List<Emitted>, File?>>>,
convert: (CompilerArguments) -> List<EitherNel<WirespecException, Pair<List<Emitted>, File?>>>,
write: List<Emitted>.(File?) -> Unit,
): (Array<out String>) -> Unit = WirespecCli().subcommands(Compile(compile, write), Convert(convert, write))::main
}
}

Expand All @@ -58,7 +65,7 @@ private abstract class CommonOptions : CliktCommand() {
val strict by option(*Options.Strict.flags, help = "Strict mode").flag()

fun getInput(inputDir: String? = null): Input =
if (inputDir != null && inputFile != null) error("Choose either a file or a directory. Not Both.")
if (inputDir != null && inputFile != null) throw CliktError("Choose either a file or a directory. Not Both.")
else inputFile
?.let(FullFilePath.Companion::parse)
?: inputDir?.let(::FullDirPath)
Expand All @@ -69,11 +76,14 @@ private abstract class CommonOptions : CliktCommand() {
"INFO" -> INFO
"WARN" -> WARN
"ERROR" -> ERROR
else -> error("Choose one of these log levels: $Level")
else -> throw CliktError("Choose one of these log levels: $Level")
}
}

private class Compile(private val block: (CompilerArguments) -> Unit) : CommonOptions() {
private class Compile(
private val block: (CompilerArguments) -> List<EitherNel<WirespecException, Pair<List<Emitted>, File?>>>,
private val write: (List<Emitted>, File?) -> Unit,
) : CommonOptions() {

private val languages by option(*Options.Language.flags, help = "Language")
.choice(Language.toMap())
Expand All @@ -89,11 +99,19 @@ private class Compile(private val block: (CompilerArguments) -> Unit) : CommonOp
logLevel = logLevel.toLogLevel(),
shared = shared,
strict = strict,
).let(block)
).let(block).forEach {
when (it) {
is Either.Right -> it.value.let { (result, file) -> write(result, file) }
is Either.Left -> throw CliktError(it.value.joinToString { e -> e.message ?: "" })
}
}
}
}

private class Convert(private val block: (CompilerArguments) -> Unit) : CommonOptions() {
private class Convert(
private val block: (CompilerArguments) -> List<EitherNel<WirespecException, Pair<List<Emitted>, File?>>>,
private val write: (List<Emitted>, File?) -> Unit,
) : CommonOptions() {

private val format by argument(help = "Input format").enum<Format>()
private val languages by option(*Options.Language.flags, help = "Language")
Expand All @@ -111,6 +129,11 @@ private class Convert(private val block: (CompilerArguments) -> Unit) : CommonOp
logLevel = logLevel.toLogLevel(),
shared = shared,
strict = strict,
).let(block)
).let(block).forEach {
when (it) {
is Either.Right -> it.value.let { (result, file) -> write(result, file) }
is Either.Left -> echo(it.value, err = true)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package community.flock.wirespec.plugin.cli

import arrow.core.Either
import arrow.core.NonEmptyList
import arrow.core.left
import arrow.core.nel
import arrow.core.right
import community.flock.wirespec.compiler.core.WirespecSpec
import community.flock.wirespec.compiler.core.compile
import community.flock.wirespec.compiler.core.component1
Expand All @@ -10,8 +14,9 @@ import community.flock.wirespec.compiler.core.emit.ScalaEmitter
import community.flock.wirespec.compiler.core.emit.TypeScriptEmitter
import community.flock.wirespec.compiler.core.emit.WirespecEmitter
import community.flock.wirespec.compiler.core.emit.common.Emitted
import community.flock.wirespec.compiler.core.emit.common.Emitter
import community.flock.wirespec.compiler.core.emit.common.Emitter.Companion.firstToUpper
import community.flock.wirespec.compiler.core.exceptions.WirespecException
import community.flock.wirespec.compiler.core.exceptions.WirespecException.IOException.FileReadException
import community.flock.wirespec.compiler.utils.Logger
import community.flock.wirespec.openapi.v2.OpenApiV2Emitter
import community.flock.wirespec.openapi.v3.OpenApiV3Emitter
Expand Down Expand Up @@ -50,12 +55,12 @@ fun main(args: Array<String>) {
(0..20)
.mapNotNull(args::orNull)
.toTypedArray()
.let(WirespecCli.provide(::compile, ::convert))
.let(WirespecCli.provide(::compile, ::convert, ::write))
}

fun convert(arguments: CompilerArguments): Unit = compile(arguments)
fun convert(arguments: CompilerArguments): List<Either<NonEmptyList<WirespecException>, Pair<List<Emitted>, File?>>> = compile(arguments)

fun compile(arguments: CompilerArguments) {
fun compile(arguments: CompilerArguments): List<Either<NonEmptyList<WirespecException>, Pair<List<Emitted>, File?>>> {

val input = arguments.input
val output = arguments.output
Expand All @@ -65,7 +70,7 @@ fun compile(arguments: CompilerArguments) {

val logger = Logger(arguments.logLevel)

when (val operation = arguments.operation) {
return when (val operation = arguments.operation) {
is Operation.Convert -> {

val fullPath = input as FullFilePath
Expand All @@ -86,7 +91,7 @@ fun compile(arguments: CompilerArguments) {
)
) to file
else results to file
}.map { (results, file) -> write(results, file) }
}.map { it.right() }
}

is Operation.Compile -> when (input) {
Expand All @@ -95,62 +100,58 @@ fun compile(arguments: CompilerArguments) {

is FullDirPath -> Directory(input.path)
.wirespecFiles()
.forEach { it.wirespec(languages, packageName, it.path.out(packageName, output), logger) }
.flatMap { it.wirespec(languages, packageName, it.path.out(packageName, output), logger) }

is FullFilePath ->
if (input.extension == FileExtension.Wirespec) WirespecFile(input)
.let { it.wirespec(languages, packageName, it.path.out(packageName, output), logger) }
else error("Path $input is not a Wirespec file")
else FileReadException("Path $input is not a Wirespec file").nel().left().nel()
}
}
}

fun write(output: List<Emitted>, file: File?) {
output.forEach { (name, result) -> file?.copy(FileName(name))?.write(result) ?: print(result) }
}

private fun Reader.wirespec(
languages: Set<Language>,
packageName: PackageName,
path: (FileExtension) -> FullFilePath,
logger: Logger
) {
read()
.let(WirespecSpec::compile)(logger)
.let { compiler ->
languages.emitters(packageName, path, logger).map { (emitter, file) ->
val results = compiler(emitter)
if (!emitter.split) results.map {
listOf(
Emitted(
path(FileExtension.Wirespec).fileName.value.firstToUpper(),
it.first().result
)
) = read()
.let(WirespecSpec::compile)(logger)
.let { compiler ->
languages.emitters(packageName, path, logger).map { (emitter, file) ->
val results = compiler(emitter)
if (!emitter.split) results.map {
listOf(
Emitted(
path(FileExtension.Wirespec).fileName.value.firstToUpper(),
it.first().result
)
} to file
else results to file
}
}
.map { (results, file) ->
when (results) {
is Either.Right -> write(results.value, file)
is Either.Left -> println(results.value)
}
}
}

private fun Set<Language>.emitters(packageName: PackageName, path: ((FileExtension) -> FullFilePath)?, logger: Logger): List<Pair<Emitter, File?>> =
map {
val (packageString) = packageName
when (it) {
Java -> JavaEmitter(packageString, logger) to path?.let { JavaFile(it(FileExtension.Java)) }
Kotlin -> KotlinEmitter(packageString, logger) to path?.let { KotlinFile(it(FileExtension.Kotlin)) }
Scala -> ScalaEmitter(packageString, logger) to path?.let { ScalaFile(it(FileExtension.Scala)) }
TypeScript -> TypeScriptEmitter(logger) to path?.let { TypeScriptFile(it(FileExtension.TypeScript)) }
Wirespec -> WirespecEmitter(logger) to path?.let { WirespecFile(it(FileExtension.Wirespec)) }
OpenAPIV2 -> OpenApiV2Emitter to path?.let { JsonFile(it(FileExtension.Json)) }
OpenAPIV3 -> OpenApiV3Emitter to path?.let { JsonFile(it(FileExtension.Json)) }
)
} to file
else results to file
}
}
.map { (results, file) -> results.map { it to file } }

private fun write(output: List<Emitted>, file: File?) {
output.forEach { (name, result) -> file?.copy(FileName(name))?.write(result) ?: print(result) }
private fun Set<Language>.emitters(
packageName: PackageName,
path: ((FileExtension) -> FullFilePath)?,
logger: Logger
) = map {
val (packageString) = packageName
when (it) {
Java -> JavaEmitter(packageString, logger) to path?.let { JavaFile(it(FileExtension.Java)) }
Kotlin -> KotlinEmitter(packageString, logger) to path?.let { KotlinFile(it(FileExtension.Kotlin)) }
Scala -> ScalaEmitter(packageString, logger) to path?.let { ScalaFile(it(FileExtension.Scala)) }
TypeScript -> TypeScriptEmitter(logger) to path?.let { TypeScriptFile(it(FileExtension.TypeScript)) }
Wirespec -> WirespecEmitter(logger) to path?.let { WirespecFile(it(FileExtension.Wirespec)) }
OpenAPIV2 -> OpenApiV2Emitter to path?.let { JsonFile(it(FileExtension.Json)) }
OpenAPIV3 -> OpenApiV3Emitter to path?.let { JsonFile(it(FileExtension.Json)) }
}
}

private fun FullFilePath.out(packageName: PackageName, output: Output?) = { extension: FileExtension ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ package community.flock.wirespec.plugin.cli.io
import community.flock.wirespec.plugin.FileName
import community.flock.wirespec.plugin.FullFilePath
import community.flock.wirespec.plugin.Reader
import community.flock.wirespec.plugin.Writer

interface Copy {
fun copy(fileName: FileName): File
}

expect abstract class File(path: FullFilePath) : Reader, Copy {
expect abstract class File(path: FullFilePath) : Reader, Writer, Copy {

val path: FullFilePath

override fun read(): String

fun write(text: String)
override fun write(string: String)

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package community.flock.wirespec.plugin.cli

import arrow.core.EitherNel
import community.flock.wirespec.compiler.core.emit.common.DEFAULT_GENERATED_PACKAGE_STRING
import community.flock.wirespec.compiler.core.emit.common.Emitted
import community.flock.wirespec.compiler.core.exceptions.WirespecException
import community.flock.wirespec.compiler.utils.Logger.Level.ERROR
import community.flock.wirespec.plugin.CompilerArguments
import community.flock.wirespec.plugin.Console
import community.flock.wirespec.plugin.FileExtension
import community.flock.wirespec.plugin.Format
Expand All @@ -10,6 +14,7 @@ import community.flock.wirespec.plugin.FullFilePath
import community.flock.wirespec.plugin.Language.Kotlin
import community.flock.wirespec.plugin.Language.Wirespec
import community.flock.wirespec.plugin.Operation
import community.flock.wirespec.plugin.cli.io.File
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeTypeOf
Expand All @@ -34,21 +39,23 @@ class CommandLineEntitiesTest {
}
)
}.filterNot { it == "-f" }.toTypedArray()
WirespecCli.provide({
it.input.shouldBeTypeOf<FullDirPath>().path shouldBe "input"
it.operation.shouldBeTypeOf<Operation.Compile>()
it.output?.value shouldBe "output"
it.languages shouldBe setOf(Wirespec)
it.packageName.value shouldBe "packageName"
it.logLevel shouldBe ERROR
it.shared.also(::println) shouldBe true
it.strict shouldBe true
}, {})(arrayOf("compile") + opts)
WirespecCli.provide(
noopInput {
it.input.shouldBeTypeOf<FullDirPath>().path shouldBe "input"
it.operation.shouldBeTypeOf<Operation.Compile>()
it.output?.value shouldBe "output"
it.languages shouldBe setOf(Wirespec)
it.packageName.value shouldBe "packageName"
it.logLevel shouldBe ERROR
it.shared.also(::println) shouldBe true
it.strict shouldBe true
}, noopInput {}, noopWriter
)(arrayOf("compile") + opts)
}

@Test
fun testMinimumCliArgumentsForCompile() {
WirespecCli.provide({
WirespecCli.provide(noopInput {
it.operation.shouldBeTypeOf<Operation.Compile>()
it.input.shouldBeTypeOf<Console>()
it.output.shouldBeNull()
Expand All @@ -57,12 +64,12 @@ class CommandLineEntitiesTest {
it.logLevel shouldBe ERROR
it.shared shouldBe false
it.strict shouldBe false
}, {})(arrayOf("compile", "-l", "Kotlin"))
}, noopInput { }, noopWriter)(arrayOf("compile", "-l", "Kotlin"))
}

@Test
fun testMinimumCliArgumentsForConvert() {
WirespecCli.provide({ }, {
WirespecCli.provide(noopInput { }, noopInput {
it.operation.shouldBeTypeOf<Operation.Convert>()
it.input.shouldBeTypeOf<FullFilePath>().run {
fileName.value shouldBe "swagger"
Expand All @@ -74,12 +81,12 @@ class CommandLineEntitiesTest {
it.logLevel shouldBe ERROR
it.shared shouldBe false
it.strict shouldBe false
})(arrayOf("convert", "-f", "swagger.json", "openapiv2"))
}, noopWriter)(arrayOf("convert", "-f", "swagger.json", "openapiv2"))
}

@Test
fun testCommandLineEntitiesParser() {
WirespecCli.provide({}, {
WirespecCli.provide(noopInput { }, noopInput {
it.operation.shouldBeTypeOf<Operation.Convert>().run {
format shouldBe Format.OpenApiV2
}
Expand All @@ -90,6 +97,15 @@ class CommandLineEntitiesTest {
it.logLevel shouldBe ERROR
it.shared shouldBe false
it.strict shouldBe false
})(arrayOf("convert", "openapiv2", "-o", "output", "-l", "Kotlin"))
}, noopWriter)(arrayOf("convert", "openapiv2", "-o", "output", "-l", "Kotlin"))
}

private fun noopInput(block: (CompilerArguments) -> Unit): (CompilerArguments) -> List<EitherNel<WirespecException, Pair<List<Emitted>, File?>>> =
{
block(it)
emptyList()
}

private val noopWriter: (List<Emitted>, File?) -> Unit = { _, _ -> }

}
Loading

0 comments on commit da7860f

Please sign in to comment.