Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • api-redesign
  • main
  • 0.10.0
  • 0.10.1
  • 0.10.2
  • 0.7.0
  • 0.8.0
  • 0.8.1
  • 0.9.0
  • 0.9.1
  • 0.9.2
11 results

Target

Select target project
  • justJanne/libquassel
1 result
Select Git revision
  • api-redesign
  • main
  • 0.10.0
  • 0.10.1
  • 0.10.2
  • 0.7.0
  • 0.8.0
  • 0.8.1
  • 0.9.0
  • 0.9.1
  • 0.9.2
11 results
Show changes
Showing
with 596 additions and 166 deletions
...@@ -13,17 +13,21 @@ import com.squareup.kotlinpoet.CodeBlock ...@@ -13,17 +13,21 @@ import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.buildCodeBlock
class WhenBlockBuilder constructor( class WhenBlockBuilder constructor(
private val over: ArgString private val over: ArgString,
) { ) {
private val cases = mutableListOf<Pair<ArgString, CodeBlock>>() private val cases = mutableListOf<Pair<ArgString, CodeBlock>>()
constructor(name: String, vararg args: Any?) : this(ArgString(name, args)) constructor(name: String, vararg args: Any?) : this(ArgString(name, args))
fun addCase(condition: ArgString, block: CodeBlock) { fun addCase(
condition: ArgString,
block: CodeBlock,
) {
cases.add(Pair(condition, block)) cases.add(Pair(condition, block))
} }
fun build(): CodeBlock = buildCodeBlock { fun build(): CodeBlock =
buildCodeBlock {
beginControlFlow("when (${over.name})", over.args) beginControlFlow("when (${over.name})", over.args)
for ((condition, code) in cases) { for ((condition, code) in cases) {
beginControlFlow("${condition.name} ->", *condition.args) beginControlFlow("${condition.name} ->", *condition.args)
...@@ -33,7 +37,11 @@ class WhenBlockBuilder constructor( ...@@ -33,7 +37,11 @@ class WhenBlockBuilder constructor(
endControlFlow() endControlFlow()
} }
inline fun addCase(name: String, vararg args: Any?, f: CodeBlock.Builder.() -> Unit) { inline fun addCase(
name: String,
vararg args: Any?,
f: CodeBlock.Builder.() -> Unit,
) {
addCase(ArgString(name, args), buildCodeBlock(f)) addCase(ArgString(name, args), buildCodeBlock(f))
} }
......
/* /*
* libquassel * Copyright (C) 2021 Zac Sweers
* Copyright (c) 2021 Janne Mareike Koschinski
* *
* This Source Code Form is subject to the terms of the Mozilla Public License, * Licensed under the Apache License, Version 2.0 (the "License");
* v. 2.0. If a copy of the MPL was not distributed with this file, You can * you may not use this file except in compliance with the License.
* obtain one at https://mozilla.org/MPL/2.0/. * You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
package de.justjanne.libquassel.generator.util.ksp package de.justjanne.libquassel.generator.util.ksp
import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSDeclaration
...@@ -26,7 +32,7 @@ private fun KSDeclaration.parents(): List<KSDeclaration> { ...@@ -26,7 +32,7 @@ private fun KSDeclaration.parents(): List<KSDeclaration> {
fun KSDeclaration.asClassName(): ClassName { fun KSDeclaration.asClassName(): ClassName {
return ClassName( return ClassName(
packageName.asString(), packageName.asString(),
parents().map { it.simpleName.asString() } parents().map { it.simpleName.asString() },
) )
} }
......
/* /*
* libquassel * libquassel
* Copyright (c) 2021 Janne Mareike Koschinski * Copyright (c) 2024 Janne Mareike Koschinski
* *
* This Source Code Form is subject to the terms of the Mozilla Public License, * This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can * v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/. * obtain one at https://mozilla.org/MPL/2.0/.
*/ */
package de.justjanne.libquassel.client.exceptions package de.justjanne.libquassel.generator.util.ksp
class IrcListException(message: String) : Exception(message) import com.google.devtools.ksp.symbol.KSClassDeclaration
internal fun KSClassDeclaration.asType() = asType(emptyList())
/* /*
* libquassel * Copyright (C) 2021 Zac Sweers
* Copyright (c) 2021 Janne Mareike Koschinski
* *
* This Source Code Form is subject to the terms of the Mozilla Public License, * Licensed under the Apache License, Version 2.0 (the "License");
* v. 2.0. If a copy of the MPL was not distributed with this file, You can * you may not use this file except in compliance with the License.
* obtain one at https://mozilla.org/MPL/2.0/. * You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
package de.justjanne.libquassel.generator.util.ksp package de.justjanne.libquassel.generator.util.ksp
import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSDeclaration
...@@ -19,8 +25,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy ...@@ -19,8 +25,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.WildcardTypeName import com.squareup.kotlinpoet.WildcardTypeName
fun KSDeclaration.asTypeName(): TypeName = fun KSDeclaration.asTypeName(): TypeName = ClassName(packageName.asString(), simpleName.asString())
ClassName(packageName.asString(), simpleName.asString())
fun KSTypeReference.asTypeName(): TypeName = resolve().asTypeName() fun KSTypeReference.asTypeName(): TypeName = resolve().asTypeName()
...@@ -36,7 +41,8 @@ fun KSType.asTypeName(): TypeName { ...@@ -36,7 +41,8 @@ fun KSType.asTypeName(): TypeName {
.copy(nullable = isMarkedNullable) .copy(nullable = isMarkedNullable)
} }
val parameters = arguments.map { val parameters =
arguments.map {
val type = it.type?.resolve() val type = it.type?.resolve()
when (it.variance) { when (it.variance) {
Variance.STAR -> Variance.STAR ->
......
...@@ -13,17 +13,14 @@ ...@@ -13,17 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package de.justjanne.libquassel.generator.util.ksp
package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSType
internal inline fun <reified T : Annotation> KSAnnotated.findAnnotationWithType( internal inline fun <reified T : Annotation> KSAnnotated.findAnnotationWithType(resolver: Resolver): KSAnnotation? {
resolver: Resolver,
): KSAnnotation? {
return findAnnotationWithType(resolver.getClassDeclarationByName<T>().asType()) return findAnnotationWithType(resolver.getClassDeclarationByName<T>().asType())
} }
......
...@@ -13,8 +13,7 @@ ...@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package de.justjanne.libquassel.generator.util.ksp
package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSClassDeclaration
......
...@@ -13,18 +13,17 @@ ...@@ -13,18 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package de.justjanne.libquassel.generator.util.ksp
package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSType
import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.ClassName
import de.justjanne.libquassel.generator.util.ksp.asTypeName
internal inline fun <reified T> KSAnnotation.getMember(name: String): T? { internal inline fun <reified T> KSAnnotation.getMember(name: String): T? {
val matchingArg = arguments.find { it.name?.asString() == name } val matchingArg =
arguments.find { it.name?.asString() == name }
?: error( ?: error(
"No member name found for '$name'. All arguments: ${arguments.map { it.name?.asString() }}" "No member name found for '$name'. All arguments: ${arguments.map { it.name?.asString() }}",
) )
return when (val argValue = matchingArg.value) { return when (val argValue = matchingArg.value) {
is T -> argValue is T -> argValue
......
...@@ -13,8 +13,7 @@ ...@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package de.justjanne.libquassel.generator.util.ksp
package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSType
......
/*
* Copyright (C) 2021 Zac Sweers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.justjanne.libquassel.generator.util.ksp
import com.google.devtools.ksp.symbol.KSType
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.asClassName
import kotlin.reflect.KClass
internal inline fun <reified T : Enum<T>> KSType.toEnum(): T? {
return asClassName().toEnum(T::class)
}
internal inline fun <reified T : Enum<T>> ClassName.toEnum(): T? {
return toEnum(T::class)
}
internal fun <T : Enum<T>> ClassName.toEnum(clazz: KClass<T>): T? {
val enumClassName = clazz.asClassName()
return clazz.java.enumConstants.find {
this.canonicalName == enumClassName.nestedClass(it.name).canonicalName
}
}
/*
* Copyright (C) 2021 Zac Sweers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.symbol.KSClassDeclaration
internal fun KSClassDeclaration.asType() = asType(emptyList())
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.util
import com.google.devtools.ksp.symbol.KSType
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.asClassName
import de.justjanne.libquassel.generator.util.ksp.asClassName
internal inline fun <reified T : Enum<T>> KSType.toEnum(): T? {
return asClassName().toEnum(T::class.java)
}
internal inline fun <reified T : Enum<T>> ClassName.toEnum(): T? {
return toEnum(T::class.java)
}
internal fun <T : Enum<T>> ClassName.toEnum(clazz: Class<T>): T? {
val enumClassName = clazz.asClassName()
return clazz.enumConstants.find {
this.canonicalName == enumClassName.nestedClass(it.name).canonicalName
}
}
...@@ -18,119 +18,135 @@ import com.squareup.kotlinpoet.PropertySpec ...@@ -18,119 +18,135 @@ import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.buildCodeBlock
import de.justjanne.libquassel.annotations.ProtocolSide import de.justjanne.libquassel.generator.Constants
import de.justjanne.libquassel.generator.Constants.TYPENAME_GENERATED import de.justjanne.libquassel.generator.Constants.TYPENAME_GENERATED
import de.justjanne.libquassel.generator.Constants.TYPENAME_INVOKER
import de.justjanne.libquassel.generator.Constants.TYPENAME_QVARIANTLIST import de.justjanne.libquassel.generator.Constants.TYPENAME_QVARIANTLIST
import de.justjanne.libquassel.generator.Constants.TYPENAME_QVARIANT_INTOORTHROW import de.justjanne.libquassel.generator.Constants.TYPENAME_QVARIANT_INTOORTHROW
import de.justjanne.libquassel.generator.Constants.TYPENAME_SYNCABLESTUB import de.justjanne.libquassel.generator.Constants.TYPENAME_RPCDISPATCHER
import de.justjanne.libquassel.generator.Constants.TYPENAME_SYNCDISPATCHER
import de.justjanne.libquassel.generator.Constants.TYPENAME_UNKNOWN_METHOD_EXCEPTION import de.justjanne.libquassel.generator.Constants.TYPENAME_UNKNOWN_METHOD_EXCEPTION
import de.justjanne.libquassel.generator.Constants.TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION
import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
import de.justjanne.libquassel.generator.rpcmodel.RpcModel import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor
import de.justjanne.libquassel.generator.util.kotlinpoet.ArgString import de.justjanne.libquassel.generator.util.kotlinpoet.ArgString
import de.justjanne.libquassel.generator.util.kotlinpoet.buildWhen import de.justjanne.libquassel.generator.util.kotlinpoet.buildWhen
import de.justjanne.libquassel.generator.util.kotlinpoet.withIndent import de.justjanne.libquassel.generator.util.kotlinpoet.withIndent
import transformName import de.justjanne.libquassel.generator.util.transformName
class RpcModelProcessor : RpcModelVisitor<ProtocolSide, KotlinModel?> { class DispatcherGenerator : RpcModelVisitor<KotlinModel?> {
override fun visitObjectModel(model: RpcModel.ObjectModel, data: ProtocolSide): KotlinModel { override fun visitObjectModel(
val name = ClassName( model: RpcModel.ObjectModel,
TYPENAME_INVOKER.packageName, ): KotlinModel.FileModel {
"${model.rpcName}${transformName(data.name)}Invoker" val name = toClassName(model)
)
return KotlinModel.FileModel( return KotlinModel.FileModel(
listOf(model.source), listOf(model.source),
FileSpec.builder(name.packageName, name.simpleName) FileSpec.builder(name.packageName, name.simpleName)
.addImport( .addImport(
TYPENAME_QVARIANT_INTOORTHROW.packageName, TYPENAME_QVARIANT_INTOORTHROW.packageName,
TYPENAME_QVARIANT_INTOORTHROW.simpleName TYPENAME_QVARIANT_INTOORTHROW.simpleName,
) )
.addAnnotation(TYPENAME_GENERATED) .addAnnotation(TYPENAME_GENERATED)
.addType( .addType(
TypeSpec.objectBuilder(name.simpleName) TypeSpec.classBuilder(name.simpleName)
.addSuperinterface(TYPENAME_INVOKER) .primaryConstructor(
.addAnnotation(TYPENAME_GENERATED) FunSpec.constructorBuilder()
.addProperty( .addAnnotation(Constants.TYPENAME_JAVAX_INJECT)
PropertySpec.builder( .addParameter(
"className", ParameterSpec.builder("api", model.name)
String::class.asTypeName(), .build()
KModifier.OVERRIDE
) )
.initializer("\"${model.rpcName}\"")
.addAnnotation(TYPENAME_GENERATED)
.build() .build()
) )
.addProperty(
PropertySpec.builder("api", model.name)
.initializer("api")
.addModifiers(KModifier.PRIVATE)
.build()
)
.addSuperinterface(if (model.rpcName.isEmpty()) TYPENAME_RPCDISPATCHER else TYPENAME_SYNCDISPATCHER)
.addAnnotation(TYPENAME_GENERATED)
.addFunction( .addFunction(
FunSpec.builder("invoke") FunSpec.builder("invoke")
.addModifiers(KModifier.OVERRIDE, KModifier.OPERATOR) .addModifiers(KModifier.OVERRIDE, KModifier.OPERATOR)
.addAnnotation(TYPENAME_GENERATED) .addAnnotation(TYPENAME_GENERATED)
.addParameter( .let {
if (model.rpcName.isNotEmpty()) {
it.addParameter(
ParameterSpec.builder( ParameterSpec.builder(
"on", "objectName",
TYPENAME_SYNCABLESTUB Constants.TYPENAME_OBJECTNAME,
).build() ).build(),
).addParameter( )
} else it
}.addParameter(
ParameterSpec.builder( ParameterSpec.builder(
"method", "method",
String::class.asTypeName() String::class.asTypeName(),
).build() ).build(),
).addParameter( ).addParameter(
ParameterSpec.builder( ParameterSpec.builder(
"params", "params",
TYPENAME_QVARIANTLIST TYPENAME_QVARIANTLIST,
).build() ).build(),
) )
.addCode( .addCode(
buildCodeBlock { buildCodeBlock {
beginControlFlow("if (on is %T)", model.name)
buildWhen("method") { buildWhen("method") {
for (method in model.methods) { for (method in model.methods) {
val block = method.accept(this@RpcModelProcessor, data) val block =
method.accept(this@DispatcherGenerator)
as? KotlinModel.FunctionModel as? KotlinModel.FunctionModel
?: continue ?: continue
addCase(ArgString("%S", method.rpcName ?: method.name), block.data) addCase(ArgString("%S", method.rpcName ?: method.name), block.data)
} }
buildElse { buildElse {
addStatement("throw %T(className, method)", TYPENAME_UNKNOWN_METHOD_EXCEPTION) addStatement("throw %T(%S, method)", TYPENAME_UNKNOWN_METHOD_EXCEPTION, model.rpcName)
}
} }
nextControlFlow("else")
addStatement("throw %T(on, className)", TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION)
endControlFlow()
} }
},
) )
.build() .build(),
) )
.build() .build(),
).build() ).build(),
) )
} }
override fun visitFunctionModel(model: RpcModel.FunctionModel, data: ProtocolSide) = override fun visitFunctionModel(
if (model.side != data) null model: RpcModel.FunctionModel,
else KotlinModel.FunctionModel( ) = KotlinModel.FunctionModel(
model.source, model.source,
buildCodeBlock { buildCodeBlock {
if (model.parameters.isEmpty()) { if (model.static && model.parameters.isEmpty()) {
addStatement("on.${model.name}()") addStatement("api.${model.name}()")
} else { } else {
addStatement("on.${model.name}(") addStatement("api.${model.name}(")
withIndent { withIndent {
val lastIndex = model.parameters.size - 1 if (!model.static) {
addStatement(
"objectName,",
)
}
for ((i, parameter) in model.parameters.withIndex()) { for ((i, parameter) in model.parameters.withIndex()) {
val suffix = if (i != lastIndex) "," else ""
addStatement( addStatement(
"${parameter.name} = params[$i].intoOrThrow<%T>()$suffix", "${parameter.name} = params[$i].intoOrThrow<%T>(),",
parameter.type parameter.type,
) )
} }
} }
addStatement(")") addStatement(")")
} }
} },
) )
override fun visitParameterModel(model: RpcModel.ParameterModel, data: ProtocolSide): KotlinModel? = null override fun visitParameterModel(
model: RpcModel.ParameterModel,
): KotlinModel? = null
companion object {
fun toClassName(model: RpcModel.ObjectModel) = ClassName(
TYPENAME_SYNCDISPATCHER.packageName,
"${model.rpcName}${transformName(model.side.name)}Dispatcher",
)
}
} }
...@@ -18,33 +18,37 @@ import com.google.devtools.ksp.symbol.KSNode ...@@ -18,33 +18,37 @@ import com.google.devtools.ksp.symbol.KSNode
import com.google.devtools.ksp.symbol.KSValueParameter import com.google.devtools.ksp.symbol.KSValueParameter
import com.google.devtools.ksp.visitor.KSEmptyVisitor import com.google.devtools.ksp.visitor.KSEmptyVisitor
import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.ClassName
import de.justjanne.libquassel.generator.Constants
import de.justjanne.libquassel.generator.annotation.RpcFunctionAnnotation import de.justjanne.libquassel.generator.annotation.RpcFunctionAnnotation
import de.justjanne.libquassel.generator.annotation.RpcObjectAnnotation import de.justjanne.libquassel.generator.annotation.RpcObjectAnnotation
import de.justjanne.libquassel.generator.annotation.RpcParameterAnnotation
import de.justjanne.libquassel.generator.rpcmodel.RpcModel import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import de.justjanne.libquassel.generator.util.ksp.asTypeName import de.justjanne.libquassel.generator.util.ksp.asTypeName
class KSDeclarationParser( class KotlinModelGenerator(
private val resolver: Resolver, private val resolver: Resolver,
private val logger: KSPLogger private val logger: KSPLogger,
) : KSEmptyVisitor<Unit, RpcModel?>() { ) : KSEmptyVisitor<Unit, RpcModel?>() {
override fun visitClassDeclaration( override fun visitClassDeclaration(
classDeclaration: KSClassDeclaration, classDeclaration: KSClassDeclaration,
data: Unit data: Unit,
): RpcModel.ObjectModel? { ): RpcModel.ObjectModel? {
val annotation = RpcObjectAnnotation.of(classDeclaration, resolver) val annotation =
RpcObjectAnnotation.of(classDeclaration, resolver)
?: return null ?: return null
try { try {
return RpcModel.ObjectModel( return RpcModel.ObjectModel(
classDeclaration, classDeclaration,
ClassName( ClassName(
classDeclaration.packageName.asString(), classDeclaration.packageName.asString(),
classDeclaration.simpleName.asString() classDeclaration.simpleName.asString(),
), ),
annotation.name, annotation.name,
annotation.side,
classDeclaration.getDeclaredFunctions() classDeclaration.getDeclaredFunctions()
.mapNotNull { it.accept(this, Unit) } .mapNotNull { it.accept(this, Unit) }
.mapNotNull { it as? RpcModel.FunctionModel } .mapNotNull { it as? RpcModel.FunctionModel }
.toList() .toList(),
) )
} catch (t: Throwable) { } catch (t: Throwable) {
logger.error("Error processing ${annotation.name}", classDeclaration) logger.error("Error processing ${annotation.name}", classDeclaration)
...@@ -55,19 +59,22 @@ class KSDeclarationParser( ...@@ -55,19 +59,22 @@ class KSDeclarationParser(
override fun visitFunctionDeclaration( override fun visitFunctionDeclaration(
function: KSFunctionDeclaration, function: KSFunctionDeclaration,
data: Unit data: Unit,
): RpcModel.FunctionModel? { ): RpcModel.FunctionModel? {
val annotation = RpcFunctionAnnotation.of(function, resolver) val annotation =
RpcFunctionAnnotation.of(function, resolver)
?: return null ?: return null
try { try {
val parameters = function.parameters
.mapNotNull { it.accept(this, Unit) }
.mapNotNull { it as? RpcModel.ParameterModel }
return RpcModel.FunctionModel( return RpcModel.FunctionModel(
function, function,
parameters.firstOrNull()?.type != Constants.TYPENAME_OBJECTNAME,
function.simpleName.asString(), function.simpleName.asString(),
annotation.name, annotation.name,
annotation.target, annotation.type,
function.parameters parameters.filter { it.type != Constants.TYPENAME_OBJECTNAME },
.mapNotNull { it.accept(this, Unit) }
.mapNotNull { it as? RpcModel.ParameterModel }
) )
} catch (t: Throwable) { } catch (t: Throwable) {
logger.error("Error processing ${annotation.name ?: function.simpleName.asString()}", function) logger.error("Error processing ${annotation.name ?: function.simpleName.asString()}", function)
...@@ -78,13 +85,15 @@ class KSDeclarationParser( ...@@ -78,13 +85,15 @@ class KSDeclarationParser(
override fun visitValueParameter( override fun visitValueParameter(
valueParameter: KSValueParameter, valueParameter: KSValueParameter,
data: Unit data: Unit,
): RpcModel.ParameterModel { ): RpcModel.ParameterModel {
try { try {
val annotation = RpcParameterAnnotation.of(valueParameter)
return RpcModel.ParameterModel( return RpcModel.ParameterModel(
valueParameter, valueParameter,
valueParameter.name?.asString(), valueParameter.name!!.asString(),
valueParameter.type.asTypeName() valueParameter.type.asTypeName(),
annotation?.type,
) )
} catch (t: Throwable) { } catch (t: Throwable) {
logger.error("Error processing ${valueParameter.name?.asString()}", valueParameter) logger.error("Error processing ${valueParameter.name?.asString()}", valueParameter)
...@@ -93,5 +102,8 @@ class KSDeclarationParser( ...@@ -93,5 +102,8 @@ class KSDeclarationParser(
} }
} }
override fun defaultHandler(node: KSNode, data: Unit): RpcModel? = null override fun defaultHandler(
node: KSNode,
data: Unit,
): RpcModel? = null
} }
...@@ -22,16 +22,20 @@ class KotlinSaver : KotlinModelVisitor<CodeGenerator, Unit> { ...@@ -22,16 +22,20 @@ class KotlinSaver : KotlinModelVisitor<CodeGenerator, Unit> {
return Dependencies(sourceFiles.size > 1, *sourceFiles.toTypedArray()) return Dependencies(sourceFiles.size > 1, *sourceFiles.toTypedArray())
} }
override fun visitFileModel(model: KotlinModel.FileModel, data: CodeGenerator) { override fun visitFileModel(
model: KotlinModel.FileModel,
data: CodeGenerator,
) {
require(model.source.isNotEmpty()) { require(model.source.isNotEmpty()) {
"Source may not be empty. Sources was empty for $model" "Source may not be empty. Sources was empty for $model"
} }
val file = try { val file =
try {
data.createNewFile( data.createNewFile(
generateDependencies(model.source), generateDependencies(model.source),
model.data.packageName, model.data.packageName,
model.data.name model.data.name,
) )
} catch (_: FileAlreadyExistsException) { } catch (_: FileAlreadyExistsException) {
return return
...@@ -47,6 +51,6 @@ class KotlinSaver : KotlinModelVisitor<CodeGenerator, Unit> { ...@@ -47,6 +51,6 @@ class KotlinSaver : KotlinModelVisitor<CodeGenerator, Unit> {
override fun visitFunctionModel( override fun visitFunctionModel(
model: KotlinModel.FunctionModel, model: KotlinModel.FunctionModel,
data: CodeGenerator data: CodeGenerator,
) = Unit ) = Unit
} }
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.visitors
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.buildCodeBlock
import com.squareup.kotlinpoet.withIndent
import de.justjanne.libquassel.generator.Constants
import de.justjanne.libquassel.generator.Constants.TYPENAME_GENERATED
import de.justjanne.libquassel.generator.Constants.TYPENAME_PROXY
import de.justjanne.libquassel.generator.annotation.RpcFunctionAnnotation
import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor
import de.justjanne.libquassel.generator.util.transformName
class ProxyGenerator : RpcModelVisitor<KotlinModel?> {
override fun visitObjectModel(
model: RpcModel.ObjectModel,
): KotlinModel.FileModel {
val name = toClassName(model)
/*
import de.justjanne.libquassel.protocol.api.ObjectName
import de.justjanne.libquassel.protocol.api.client.AliasManagerClientApi
import de.justjanne.libquassel.protocol.api.proxy.Proxy
import de.justjanne.libquassel.protocol.models.types.QtType
import de.justjanne.libquassel.protocol.variant.qVariant
import javax.inject.Inject
class AliasManagerClientApiProxy @Inject constructor(
private val proxy: Proxy
): AliasManagerClientApi {
override fun addAlias(name: String, expansion: String) =
proxy.sync(
"AliasManager",
ObjectName.EMPTY,
"addAlias",
qVariant(name, QtType.QString),
qVariant(expansion, QtType.QString),
)
override fun requestUpdate(properties: QVariantMap) =
proxy.sync(
"AliasManager",
ObjectName.EMPTY,
"requestUpdate",
qVariant(properties, QtType.QVariantMap)
)
}
*/
return KotlinModel.FileModel(
listOf(model.source),
FileSpec.builder(name.packageName, name.simpleName)
.addImport(
"de.justjanne.libquassel.protocol.variant",
"qVariant",
)
.addAnnotation(TYPENAME_GENERATED)
.addType(
TypeSpec.classBuilder(name.simpleName)
.primaryConstructor(
FunSpec.constructorBuilder()
.addAnnotation(Constants.TYPENAME_JAVAX_INJECT)
.addParameter(
ParameterSpec.builder("proxy", TYPENAME_PROXY)
.build()
)
.build()
)
.addProperty(
PropertySpec.builder("proxy", TYPENAME_PROXY)
.initializer("proxy")
.addModifiers(KModifier.PRIVATE)
.build()
)
.addSuperinterface(model.name)
.addAnnotation(TYPENAME_GENERATED)
.addFunctions(
model.methods.map { method ->
FunSpec.builder(method.name)
.addModifiers(KModifier.OVERRIDE)
.let {
if (!method.static) {
it.addParameter(
ParameterSpec.builder("objectName", Constants.TYPENAME_OBJECTNAME)
.build()
)
} else it
}
.addParameters(
method.parameters.map { param ->
ParameterSpec.builder(param.name, param.type)
.build()
}
)
.addCode(
buildCodeBlock {
if (model.rpcName.isEmpty()) {
addStatement("proxy.rpc(")
} else {
addStatement("proxy.sync(")
}
withIndent {
if (model.rpcName.isNotEmpty()) {
addStatement("%S,", model.rpcName)
if (!method.static) {
addStatement("objectName,")
} else {
addStatement("%T.%N,", Constants.TYPENAME_OBJECTNAME, Constants.MEMBERNAME_OBJECTNAME_EMPTY)
}
}
addStatement("%S,", method.rpcName)
for (parameter in method.parameters) {
if (parameter.rpcType != null) {
addStatement(
"%N(%N, %T.%N),",
MemberName("de.justjanne.libquassel.protocol.variant", "qVariant"),
parameter.name,
parameter.rpcType.enclosingClassName!!,
parameter.rpcType,
)
} else {
addStatement(
"%N(%N),",
MemberName("de.justjanne.libquassel.protocol.variant", "qVariant"),
parameter.name,
)
}
}
}
addStatement(")")
}
)
.build()
}
)
.build(),
).build(),
)
}
override fun visitFunctionModel(
model: RpcModel.FunctionModel,
) = null
override fun visitParameterModel(
model: RpcModel.ParameterModel,
): KotlinModel? = null
companion object {
fun toClassName(model: RpcModel.ObjectModel) = ClassName(
TYPENAME_PROXY.packageName,
"${model.rpcName}${transformName(model.side.name)}Proxy",
)
}
}
...@@ -12,12 +12,20 @@ package de.justjanne.libquassel.generator.visitors ...@@ -12,12 +12,20 @@ package de.justjanne.libquassel.generator.visitors
import de.justjanne.libquassel.generator.rpcmodel.RpcModel import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor
class RpcObjectCollector : RpcModelVisitor<Unit, Unit> { class RpcModelCollector : RpcModelVisitor<Unit> {
val objects = mutableListOf<RpcModel.ObjectModel>() val objects = mutableListOf<RpcModel.ObjectModel>()
override fun visitObjectModel(model: RpcModel.ObjectModel, data: Unit) {
override fun visitObjectModel(
model: RpcModel.ObjectModel,
) {
objects.add(model) objects.add(model)
} }
override fun visitFunctionModel(model: RpcModel.FunctionModel, data: Unit) = Unit override fun visitFunctionModel(
override fun visitParameterModel(model: RpcModel.ParameterModel, data: Unit) = Unit model: RpcModel.FunctionModel,
) = Unit
override fun visitParameterModel(
model: RpcModel.ParameterModel,
) = Unit
} }
...@@ -10,15 +10,18 @@ ...@@ -10,15 +10,18 @@
package de.justjanne.libquassel.irc package de.justjanne.libquassel.irc
object HostmaskHelper { object HostmaskHelper {
fun nick(mask: String) = mask fun nick(mask: String) =
mask
.substringBeforeLast('@') .substringBeforeLast('@')
.substringBefore('!') .substringBefore('!')
fun user(mask: String) = mask fun user(mask: String) =
mask
.substringBeforeLast('@') .substringBeforeLast('@')
.substringAfter('!', missingDelimiterValue = "") .substringAfter('!', missingDelimiterValue = "")
fun host(mask: String) = mask fun host(mask: String) =
mask
.substringAfterLast('@', missingDelimiterValue = "") .substringAfterLast('@', missingDelimiterValue = "")
fun split(mask: String): Triple<String, String, String> { fun split(mask: String): Triple<String, String, String> {
...@@ -31,7 +34,11 @@ object HostmaskHelper { ...@@ -31,7 +34,11 @@ object HostmaskHelper {
return Triple(nick, user, host) return Triple(nick, user, host)
} }
fun build(nick: String, user: String?, host: String?) = buildString { fun build(
nick: String,
user: String?,
host: String?,
) = buildString {
append(nick) append(nick)
if (!user.isNullOrEmpty()) { if (!user.isNullOrEmpty()) {
append("!$user") append("!$user")
......
...@@ -13,37 +13,46 @@ import java.util.Locale ...@@ -13,37 +13,46 @@ import java.util.Locale
abstract class IrcCaseMapper { abstract class IrcCaseMapper {
@JvmName("equalsIgnoreCaseNonNull") @JvmName("equalsIgnoreCaseNonNull")
fun equalsIgnoreCase(a: String?, b: String?) = fun equalsIgnoreCase(
a == null && b == null || (a != null && b != null && equalsIgnoreCase(a, b)) a: String?,
b: String?,
) = a == null && b == null || (a != null && b != null && equalsIgnoreCase(a, b))
abstract fun equalsIgnoreCase(a: String, b: String): Boolean abstract fun equalsIgnoreCase(
a: String,
b: String,
): Boolean
@JvmName("toLowerCaseNonNull") @JvmName("toLowerCaseNonNull")
fun toLowerCase(value: String?): String? = value fun toLowerCase(value: String?): String? =
value
?.let(this@IrcCaseMapper::toLowerCase) ?.let(this@IrcCaseMapper::toLowerCase)
abstract fun toLowerCase(value: String): String abstract fun toLowerCase(value: String): String
@JvmName("toUpperCaseNonNull") @JvmName("toUpperCaseNonNull")
fun toUpperCase(value: String?): String? = value fun toUpperCase(value: String?): String? =
value
?.let(this@IrcCaseMapper::toUpperCase) ?.let(this@IrcCaseMapper::toUpperCase)
abstract fun toUpperCase(value: String): String abstract fun toUpperCase(value: String): String
object Unicode : IrcCaseMapper() { object Unicode : IrcCaseMapper() {
override fun equalsIgnoreCase(a: String, b: String): Boolean = override fun equalsIgnoreCase(
a.equals(b, ignoreCase = true) a: String,
b: String,
): Boolean = a.equals(b, ignoreCase = true)
override fun toLowerCase(value: String): String = override fun toLowerCase(value: String): String = value.lowercase(Locale.ROOT)
value.lowercase(Locale.ROOT)
override fun toUpperCase(value: String): String = override fun toUpperCase(value: String): String = value.uppercase(Locale.ROOT)
value.uppercase(Locale.ROOT)
} }
object Rfc1459 : IrcCaseMapper() { object Rfc1459 : IrcCaseMapper() {
override fun equalsIgnoreCase(a: String, b: String): Boolean = override fun equalsIgnoreCase(
toLowerCase(a) == toLowerCase(b) || toUpperCase(a) == toUpperCase(b) a: String,
b: String,
): Boolean = toLowerCase(a) == toLowerCase(b) || toUpperCase(a) == toUpperCase(b)
override fun toLowerCase(value: String): String = override fun toLowerCase(value: String): String =
value.lowercase(Locale.ROOT) value.lowercase(Locale.ROOT)
...@@ -60,7 +69,10 @@ abstract class IrcCaseMapper { ...@@ -60,7 +69,10 @@ abstract class IrcCaseMapper {
companion object { companion object {
operator fun get(caseMapping: String?) = operator fun get(caseMapping: String?) =
if (caseMapping.equals("rfc1459", ignoreCase = true)) Rfc1459 if (caseMapping.equals("rfc1459", ignoreCase = true)) {
else Unicode Rfc1459
} else {
Unicode
}
} }
} }
...@@ -14,9 +14,10 @@ import de.justjanne.libquassel.irc.extensions.joinString ...@@ -14,9 +14,10 @@ import de.justjanne.libquassel.irc.extensions.joinString
object IrcFormat { object IrcFormat {
data class Span( data class Span(
val content: String, val content: String,
val style: Style = Style() val style: Style = Style(),
) { ) {
override fun toString(): String = joinString(", ", "Info(", ")") { override fun toString(): String =
joinString(", ", "Info(", ")") {
append(content) append(content)
if (style != Style()) { if (style != Style()) {
append("style=$style") append("style=$style")
...@@ -29,11 +30,13 @@ object IrcFormat { ...@@ -29,11 +30,13 @@ object IrcFormat {
val foreground: Color? = null, val foreground: Color? = null,
val background: Color? = null, val background: Color? = null,
) { ) {
fun flipFlag(flag: Flag) = copy( fun flipFlag(flag: Flag) =
flags = if (flags.contains(flag)) flags - flag else flags + flag copy(
flags = if (flags.contains(flag)) flags - flag else flags + flag,
) )
override fun toString(): String = joinString(", ", "Info(", ")") { override fun toString(): String =
joinString(", ", "Info(", ")") {
if (flags.isNotEmpty()) { if (flags.isNotEmpty()) {
append("flags=$flags") append("flags=$flags")
} }
...@@ -62,6 +65,6 @@ object IrcFormat { ...@@ -62,6 +65,6 @@ object IrcFormat {
UNDERLINE, UNDERLINE,
STRIKETHROUGH, STRIKETHROUGH,
MONOSPACE, MONOSPACE,
INVERSE INVERSE,
} }
} }