Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d5536fc
feat(codegen): Placeholder files
KCreate Apr 28, 2023
c3dd1ca
feat(codegen): Emit record and enum declarations
KCreate May 11, 2023
33ac176
refactor(semantic): Changed root package name
KCreate May 12, 2023
32e7639
feat(codegen): Emit single file per package
KCreate May 12, 2023
243a618
feat(codegen): Emit service declaration interfaces
KCreate May 12, 2023
17fe0bb
refactor(codegen): Codestyle
KCreate May 12, 2023
94efe2d
feat(codegen): emit rudimentary ktor provider
PascalHonegger May 13, 2023
d959011
refactor(codegen): separate files into smaller chunks
PascalHonegger May 13, 2023
8a7f885
feat(codegen): Parse http transport config
KCreate May 16, 2023
2deb891
feat(codegen): Parse faults to status code mapping
KCreate May 16, 2023
14498bc
feat(codegen): location aware transport errors
KCreate May 18, 2023
cec91b6
feat(codegen): Emit ktor server file
KCreate May 19, 2023
39e4fc9
feat(codegen): basic consumer generation
KCreate May 22, 2023
28b3172
feat(cli): parse samt config and resolve paths relative to project root
PascalHonegger May 19, 2023
e17db6a
feat(codegen): output configured generator to file system
PascalHonegger May 20, 2023
f214cd1
feat(codegen): basic mapping code generation
PascalHonegger May 20, 2023
0513a8f
feat(codegen): advanced provider code generation
PascalHonegger May 21, 2023
a71fd52
feat(codegen): move files around
PascalHonegger May 21, 2023
3d81850
feat(codegen): use objects for singletons
PascalHonegger May 21, 2023
c3fba3a
feat(codegen): fully functional provider generation
PascalHonegger May 21, 2023
d078bf4
feat(codegen): dedicated consumer generator
PascalHonegger May 22, 2023
a7b0ae8
feat(codegen): Consumer request encoding
KCreate May 23, 2023
8947dab
feat(semantic): support async operations
PascalHonegger May 27, 2023
a3b3d21
refactor(codegen): abstraction layer around config parsing
PascalHonegger May 27, 2023
110dcc4
feat(codegen): implement more consumer features
PascalHonegger May 27, 2023
dbf8de4
feat(codegen): correctly generate a variety of use-cases
PascalHonegger May 28, 2023
aad7a16
feat(codegen): change syntax of http configuration
PascalHonegger May 28, 2023
bdd53f8
feat(examples): model OpenAPI petstore in SAMT
PascalHonegger May 28, 2023
0baae45
feat(codegen): correctly generate petstore
PascalHonegger May 29, 2023
01391d8
feat(codegen): fix style issues
PascalHonegger May 29, 2023
7a82c8f
feat(semantic): fix style issues
PascalHonegger May 29, 2023
788e778
fix(codegen): fix regex constraint check
mjossdev May 29, 2023
acdf47a
feat(codegen): only parse provider once per generation cycle
PascalHonegger May 29, 2023
0d60e04
refactor(public-api): dedicated public API
PascalHonegger May 29, 2023
f9ea43c
feat(codegen): add unit tests
PascalHonegger May 29, 2023
43ad5ec
fix(cli): create all missing parent directories
PascalHonegger May 29, 2023
6d649b5
fix(codegen): correctly handle unimplemented async operation
PascalHonegger May 29, 2023
8fc4914
feat(codegen): add more types to test model
PascalHonegger May 29, 2023
109354f
feat(codegen): add oneway operation to test
PascalHonegger May 29, 2023
9509b25
feat(codegen): ensure test works across operating systems
PascalHonegger May 29, 2023
c35ead5
feat(codegen): allow multiple spaces in transport configuration
PascalHonegger May 30, 2023
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ build
ehthumbs.db
Thumbs.db

# SAMT wrapper generated files #
.samt
samtw
samtw.bat

# Random files used for debugging
specification/examples/debug.samt
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
kover(project(":cli"))
kover(project(":language-server"))
kover(project(":samt-config"))
kover(project(":codegen"))
}

koverReport {
Expand Down
3 changes: 3 additions & 0 deletions cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ dependencies {
implementation(project(":lexer"))
implementation(project(":parser"))
implementation(project(":semantic"))
implementation(project(":samt-config"))
implementation(project(":codegen"))
implementation(project(":public-api"))
}

application {
Expand Down
8 changes: 4 additions & 4 deletions cli/src/main/kotlin/tools/samt/cli/CliArgs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class CliArgs {

@Parameters(commandDescription = "Compile SAMT files")
class CompileCommand {
@Parameter(description = "Files to compile, defaults to all .samt files in the current directory")
var files: List<String> = mutableListOf()
@Parameter(description = "SAMT project to compile, defaults to the 'samt.yaml' file in the current directory")
var file: String = "./samt.yaml"
}

@Parameters(commandDescription = "Dump SAMT files in various formats for debugging purposes")
Expand All @@ -25,8 +25,8 @@ class DumpCommand {
@Parameter(names = ["--types"], description = "Dump a visual representation of the resolved types")
var dumpTypes: Boolean = false

@Parameter(description = "Files to dump, defaults to all .samt files in the current directory")
var files: List<String> = mutableListOf()
@Parameter(description = "SAMT project to dump, defaults to the 'samt.yaml' file in the current directory")
var file: String = "./samt.yaml"
}

@Parameters(commandDescription = "Initialize or update the SAMT wrapper")
Expand Down
36 changes: 33 additions & 3 deletions cli/src/main/kotlin/tools/samt/cli/CliCompiler.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
package tools.samt.cli

import tools.samt.codegen.Codegen
import tools.samt.common.DiagnosticController
import tools.samt.common.DiagnosticException
import tools.samt.common.collectSamtFiles
import tools.samt.common.readSamtSource
import tools.samt.lexer.Lexer
import tools.samt.parser.Parser
import tools.samt.semantic.SemanticModel
import java.io.IOException
import kotlin.io.path.isDirectory
import kotlin.io.path.notExists

internal fun compile(command: CompileCommand, controller: DiagnosticController) {
val sourceFiles = command.files.readSamtSourceFiles(controller)
val (configuration ,_) = CliConfigParser.readConfig(command.file, controller) ?: return

if (configuration.source.notExists() || !configuration.source.isDirectory()) {
controller.reportGlobalError("Source path '${configuration.source.toUri()}' does not point to valid directory")
return
}

val sourceFiles = collectSamtFiles(configuration.source.toUri()).readSamtSource(controller)

if (controller.hasErrors()) {
return
Expand Down Expand Up @@ -40,7 +53,24 @@ internal fun compile(command: CompileCommand, controller: DiagnosticController)
}

// build up the semantic model from the AST
SemanticModel.build(fileNodes, controller)
val model = SemanticModel.build(fileNodes, controller)

// Code Generators will be called here
// if the semantic model failed to build, exit
if (controller.hasErrors()) {
return
}

if (configuration.generators.isEmpty()) {
controller.reportGlobalInfo("No generators configured, did you forget to add a 'generators' section to the 'samt.yaml' configuration?")
return
}

for (generator in configuration.generators) {
val files = Codegen.generate(model, generator, controller)
try {
OutputWriter.write(generator.output, files)
} catch (e: IOException) {
controller.reportGlobalError("Failed to write output for generator '${generator.name}': ${e.message}")
}
}
}
41 changes: 41 additions & 0 deletions cli/src/main/kotlin/tools/samt/cli/CliConfigParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package tools.samt.cli

import tools.samt.common.DiagnosticController
import tools.samt.common.SamtConfiguration
import tools.samt.common.SamtLinterConfiguration
import tools.samt.config.SamtConfigurationParser
import java.nio.file.InvalidPathException
import kotlin.io.path.Path
import kotlin.io.path.notExists

internal object CliConfigParser {
fun readConfig(file: String, controller: DiagnosticController): Pair<SamtConfiguration, SamtLinterConfiguration>? {
val configFile = try {
Path(file)
} catch (e: InvalidPathException) {
controller.reportGlobalError("Invalid path '${file}': ${e.message}")
return null
}
if (configFile.notExists()) {
controller.reportGlobalInfo("Configuration file '${configFile.toUri()}' does not exist, using default configuration")
}
val configuration = try {
SamtConfigurationParser.parseConfiguration(configFile)
} catch (e: Exception) {
controller.reportGlobalError("Failed to parse configuration file '${configFile.toUri()}': ${e.message}")
return null
}
val samtLintConfigFile = configFile.resolveSibling(".samtrc.yaml")
if (samtLintConfigFile.notExists()) {
controller.reportGlobalInfo("Lint configuration file '${samtLintConfigFile.toUri()}' does not exist, using default lint configuration")
}
val linterConfiguration = try {
SamtConfigurationParser.parseLinterConfiguration(samtLintConfigFile)
} catch (e: Exception) {
controller.reportGlobalError("Failed to parse lint configuration file '${samtLintConfigFile.toUri()}': ${e.message}")
return null
}

return Pair(configuration, linterConfiguration)
}
}
17 changes: 16 additions & 1 deletion cli/src/main/kotlin/tools/samt/cli/CliDumper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,27 @@ package tools.samt.cli
import com.github.ajalt.mordant.terminal.Terminal
import tools.samt.common.DiagnosticController
import tools.samt.common.DiagnosticException
import tools.samt.common.collectSamtFiles
import tools.samt.common.readSamtSource
import tools.samt.lexer.Lexer
import tools.samt.parser.Parser
import tools.samt.semantic.SemanticModel
import kotlin.io.path.isDirectory
import kotlin.io.path.notExists

internal fun dump(command: DumpCommand, terminal: Terminal, controller: DiagnosticController) {
val sourceFiles = command.files.readSamtSourceFiles(controller)
val (configuration ,_) = CliConfigParser.readConfig(command.file, controller) ?: return

if (configuration.source.notExists() || !configuration.source.isDirectory()) {
controller.reportGlobalError("Source path '${configuration.source.toUri()}' does not point to valid directory")
return
}

val sourceFiles = collectSamtFiles(configuration.source.toUri()).readSamtSource(controller)

if (controller.hasErrors()) {
return
}

if (controller.hasErrors()) {
return
Expand Down
12 changes: 0 additions & 12 deletions cli/src/main/kotlin/tools/samt/cli/CliFileResolution.kt

This file was deleted.

3 changes: 0 additions & 3 deletions cli/src/main/kotlin/tools/samt/cli/DiagnosticFormatter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ internal class DiagnosticFormatter(
companion object {
private const val CONTEXT_ROW_COUNT = 3

// FIXME: this is a bit of a hack to get the terminal width
// it also means we're assuming this output will only ever be printed in a terminal
// i don't actually know what happens if it doesn't run in a tty setting
fun format(controller: DiagnosticController, startTimestamp: Long, currentTimestamp: Long, terminalWidth: Int = Terminal().info.width): String {
val formatter = DiagnosticFormatter(controller, startTimestamp, currentTimestamp, terminalWidth)
return formatter.format()
Expand Down
39 changes: 39 additions & 0 deletions cli/src/main/kotlin/tools/samt/cli/OutputWriter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package tools.samt.cli

import tools.samt.api.plugin.CodegenFile
import java.io.IOException
import java.nio.file.InvalidPathException
import java.nio.file.Path
import kotlin.io.path.*

internal object OutputWriter {
@Throws(IOException::class)
fun write(outputDirectory: Path, files: List<CodegenFile>) {
if (!outputDirectory.exists()) {
try {
outputDirectory.createDirectories()
} catch (e: IOException) {
throw IOException("Failed to create output directory '${outputDirectory}'", e)
}
}
if (!outputDirectory.isDirectory()) {
throw IOException("Path '${outputDirectory}' does not point to a directory")
}
for (file in files) {
val outputFile = try {
outputDirectory.resolve(file.filepath)
} catch (e: InvalidPathException) {
throw IOException("Invalid path '${file.filepath}'", e)
}
try {
outputFile.parent.createDirectories()
if (outputFile.notExists()) {
outputFile.createFile()
}
outputFile.writeText(file.source)
} catch (e: IOException) {
throw IOException("Failed to write file '${outputFile.toUri()}'", e)
}
}
}
}
7 changes: 6 additions & 1 deletion cli/src/main/kotlin/tools/samt/cli/TypePrinter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import tools.samt.semantic.Package

internal object TypePrinter {
fun dump(samtPackage: Package): String = buildString {
appendLine(blue(samtPackage.name.ifEmpty { "<root>" }))
if (samtPackage.isRootPackage) {
appendLine(red("<root>"))
} else {
appendLine(blue(samtPackage.name))
}

for (enum in samtPackage.enums) {
appendLine(" ${bold("enum")} ${yellow(enum.name)}")
}
Expand Down
13 changes: 13 additions & 0 deletions codegen/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id("samt-core.kotlin-conventions")
alias(libs.plugins.kover)
}

dependencies {
implementation(project(":common"))
implementation(project(":parser"))
implementation(project(":semantic"))
implementation(project(":public-api"))
testImplementation(project(":lexer"))
testImplementation(project(":samt-config"))
}
61 changes: 61 additions & 0 deletions codegen/src/main/kotlin/tools/samt/codegen/Codegen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package tools.samt.codegen

import tools.samt.api.plugin.CodegenFile
import tools.samt.api.plugin.Generator
import tools.samt.api.plugin.GeneratorParams
import tools.samt.api.plugin.TransportConfigurationParser
import tools.samt.api.types.SamtPackage
import tools.samt.codegen.http.HttpTransportConfigurationParser
import tools.samt.codegen.kotlin.KotlinTypesGenerator
import tools.samt.codegen.kotlin.ktor.KotlinKtorConsumerGenerator
import tools.samt.codegen.kotlin.ktor.KotlinKtorProviderGenerator
import tools.samt.common.DiagnosticController
import tools.samt.common.SamtGeneratorConfiguration
import tools.samt.semantic.SemanticModel

object Codegen {
private val generators: List<Generator> = listOf(
KotlinTypesGenerator,
KotlinKtorProviderGenerator,
KotlinKtorConsumerGenerator,
)

private val transports: List<TransportConfigurationParser> = listOf(
HttpTransportConfigurationParser,
)

internal class SamtGeneratorParams(
semanticModel: SemanticModel,
private val controller: DiagnosticController,
override val options: Map<String, String>,
) : GeneratorParams {
private val apiMapper = PublicApiMapper(transports, controller)
override val packages: List<SamtPackage> = semanticModel.global.allSubPackages.map { apiMapper.toPublicApi(it) }

override fun reportError(message: String) {
controller.reportGlobalError(message)
}

override fun reportWarning(message: String) {
controller.reportGlobalWarning(message)
}

override fun reportInfo(message: String) {
controller.reportGlobalInfo(message)
}
}

fun generate(
semanticModel: SemanticModel,
configuration: SamtGeneratorConfiguration,
controller: DiagnosticController,
): List<CodegenFile> {
val matchingGenerators = generators.filter { it.name == configuration.name }
when (matchingGenerators.size) {
0 -> controller.reportGlobalError("No matching generator found for '${configuration.name}'")
1 -> return matchingGenerators.single().generate(SamtGeneratorParams(semanticModel, controller, configuration.options))
else -> controller.reportGlobalError("Multiple matching generators found for '${configuration.name}'")
}
return emptyList()
}
}
Loading