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,27 +13,35 @@ import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.buildCodeBlock
class WhenBlockBuilder constructor(
private val over: ArgString
private val over: ArgString,
) {
private val cases = mutableListOf<Pair<ArgString, CodeBlock>>()
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))
}
fun build(): CodeBlock = buildCodeBlock {
beginControlFlow("when (${over.name})", over.args)
for ((condition, code) in cases) {
beginControlFlow("${condition.name} ->", *condition.args)
add(code)
fun build(): CodeBlock =
buildCodeBlock {
beginControlFlow("when (${over.name})", over.args)
for ((condition, code) in cases) {
beginControlFlow("${condition.name} ->", *condition.args)
add(code)
endControlFlow()
}
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))
}
......
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (C) 2021 Zac Sweers
*
* 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/.
* 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.KSDeclaration
......@@ -26,7 +32,7 @@ private fun KSDeclaration.parents(): List<KSDeclaration> {
fun KSDeclaration.asClassName(): ClassName {
return ClassName(
packageName.asString(),
parents().map { it.simpleName.asString() }
parents().map { it.simpleName.asString() },
)
}
......
/*
* 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,
* 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.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 Janne Mareike Koschinski
* Copyright (C) 2021 Zac Sweers
*
* 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/.
* 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.KSDeclaration
......@@ -19,8 +25,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.WildcardTypeName
fun KSDeclaration.asTypeName(): TypeName =
ClassName(packageName.asString(), simpleName.asString())
fun KSDeclaration.asTypeName(): TypeName = ClassName(packageName.asString(), simpleName.asString())
fun KSTypeReference.asTypeName(): TypeName = resolve().asTypeName()
......@@ -36,23 +41,24 @@ fun KSType.asTypeName(): TypeName {
.copy(nullable = isMarkedNullable)
}
val parameters = arguments.map {
val type = it.type?.resolve()
when (it.variance) {
Variance.STAR ->
WildcardTypeName.producerOf(Any::class)
.copy(nullable = true)
Variance.INVARIANT ->
type!!.asTypeName()
.copy(nullable = type.isMarkedNullable)
Variance.COVARIANT ->
WildcardTypeName.producerOf(type!!.asTypeName())
.copy(nullable = type.isMarkedNullable)
Variance.CONTRAVARIANT ->
WildcardTypeName.consumerOf(type!!.asTypeName())
.copy(nullable = type.isMarkedNullable)
val parameters =
arguments.map {
val type = it.type?.resolve()
when (it.variance) {
Variance.STAR ->
WildcardTypeName.producerOf(Any::class)
.copy(nullable = true)
Variance.INVARIANT ->
type!!.asTypeName()
.copy(nullable = type.isMarkedNullable)
Variance.COVARIANT ->
WildcardTypeName.producerOf(type!!.asTypeName())
.copy(nullable = type.isMarkedNullable)
Variance.CONTRAVARIANT ->
WildcardTypeName.consumerOf(type!!.asTypeName())
.copy(nullable = type.isMarkedNullable)
}
}
}
return baseType.parameterizedBy(parameters)
.copy(nullable = isMarkedNullable)
......
......@@ -13,17 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.justjanne.libquassel.generator.util
package de.justjanne.libquassel.generator.util.ksp
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSType
internal inline fun <reified T : Annotation> KSAnnotated.findAnnotationWithType(
resolver: Resolver,
): KSAnnotation? {
internal inline fun <reified T : Annotation> KSAnnotated.findAnnotationWithType(resolver: Resolver): KSAnnotation? {
return findAnnotationWithType(resolver.getClassDeclarationByName<T>().asType())
}
......
......@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.justjanne.libquassel.generator.util
package de.justjanne.libquassel.generator.util.ksp
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSClassDeclaration
......
......@@ -13,19 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.justjanne.libquassel.generator.util
package de.justjanne.libquassel.generator.util.ksp
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSType
import com.squareup.kotlinpoet.ClassName
import de.justjanne.libquassel.generator.util.ksp.asTypeName
internal inline fun <reified T> KSAnnotation.getMember(name: String): T? {
val matchingArg = arguments.find { it.name?.asString() == name }
?: error(
"No member name found for '$name'. All arguments: ${arguments.map { it.name?.asString() }}"
)
val matchingArg =
arguments.find { it.name?.asString() == name }
?: error(
"No member name found for '$name'. All arguments: ${arguments.map { it.name?.asString() }}",
)
return when (val argValue = matchingArg.value) {
is T -> argValue
is KSType ->
......
......@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.justjanne.libquassel.generator.util
package de.justjanne.libquassel.generator.util.ksp
import com.google.devtools.ksp.symbol.KSAnnotated
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
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
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_INVOKER
import de.justjanne.libquassel.generator.Constants.TYPENAME_QVARIANTLIST
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_WRONG_OBJECT_TYPE_EXCEPTION
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.kotlinpoet.ArgString
import de.justjanne.libquassel.generator.util.kotlinpoet.buildWhen
import de.justjanne.libquassel.generator.util.kotlinpoet.withIndent
import transformName
import de.justjanne.libquassel.generator.util.transformName
class RpcModelProcessor : RpcModelVisitor<ProtocolSide, KotlinModel?> {
override fun visitObjectModel(model: RpcModel.ObjectModel, data: ProtocolSide): KotlinModel {
val name = ClassName(
TYPENAME_INVOKER.packageName,
"${model.rpcName}${transformName(data.name)}Invoker"
)
class DispatcherGenerator : RpcModelVisitor<KotlinModel?> {
override fun visitObjectModel(
model: RpcModel.ObjectModel,
): KotlinModel.FileModel {
val name = toClassName(model)
return KotlinModel.FileModel(
listOf(model.source),
FileSpec.builder(name.packageName, name.simpleName)
.addImport(
TYPENAME_QVARIANT_INTOORTHROW.packageName,
TYPENAME_QVARIANT_INTOORTHROW.simpleName
TYPENAME_QVARIANT_INTOORTHROW.simpleName,
)
.addAnnotation(TYPENAME_GENERATED)
.addType(
TypeSpec.objectBuilder(name.simpleName)
.addSuperinterface(TYPENAME_INVOKER)
.addAnnotation(TYPENAME_GENERATED)
TypeSpec.classBuilder(name.simpleName)
.primaryConstructor(
FunSpec.constructorBuilder()
.addAnnotation(Constants.TYPENAME_JAVAX_INJECT)
.addParameter(
ParameterSpec.builder("api", model.name)
.build()
)
.build()
)
.addProperty(
PropertySpec.builder(
"className",
String::class.asTypeName(),
KModifier.OVERRIDE
)
.initializer("\"${model.rpcName}\"")
.addAnnotation(TYPENAME_GENERATED)
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(
FunSpec.builder("invoke")
.addModifiers(KModifier.OVERRIDE, KModifier.OPERATOR)
.addAnnotation(TYPENAME_GENERATED)
.addParameter(
ParameterSpec.builder(
"on",
TYPENAME_SYNCABLESTUB
).build()
).addParameter(
.let {
if (model.rpcName.isNotEmpty()) {
it.addParameter(
ParameterSpec.builder(
"objectName",
Constants.TYPENAME_OBJECTNAME,
).build(),
)
} else it
}.addParameter(
ParameterSpec.builder(
"method",
String::class.asTypeName()
).build()
String::class.asTypeName(),
).build(),
).addParameter(
ParameterSpec.builder(
"params",
TYPENAME_QVARIANTLIST
).build()
TYPENAME_QVARIANTLIST,
).build(),
)
.addCode(
buildCodeBlock {
beginControlFlow("if (on is %T)", model.name)
buildWhen("method") {
for (method in model.methods) {
val block = method.accept(this@RpcModelProcessor, data)
as? KotlinModel.FunctionModel
?: continue
val block =
method.accept(this@DispatcherGenerator)
as? KotlinModel.FunctionModel
?: continue
addCase(ArgString("%S", method.rpcName ?: method.name), block.data)
}
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) =
if (model.side != data) null
else KotlinModel.FunctionModel(
model.source,
buildCodeBlock {
if (model.parameters.isEmpty()) {
addStatement("on.${model.name}()")
} else {
addStatement("on.${model.name}(")
withIndent {
val lastIndex = model.parameters.size - 1
for ((i, parameter) in model.parameters.withIndex()) {
val suffix = if (i != lastIndex) "," else ""
addStatement(
"${parameter.name} = params[$i].intoOrThrow<%T>()$suffix",
parameter.type
)
}
override fun visitFunctionModel(
model: RpcModel.FunctionModel,
) = KotlinModel.FunctionModel(
model.source,
buildCodeBlock {
if (model.static && model.parameters.isEmpty()) {
addStatement("api.${model.name}()")
} else {
addStatement("api.${model.name}(")
withIndent {
if (!model.static) {
addStatement(
"objectName,",
)
}
for ((i, parameter) in model.parameters.withIndex()) {
addStatement(
"${parameter.name} = params[$i].intoOrThrow<%T>(),",
parameter.type,
)
}
addStatement(")")
}
addStatement(")")
}
)
},
)
override fun visitParameterModel(
model: RpcModel.ParameterModel,
): KotlinModel? = null
override fun visitParameterModel(model: RpcModel.ParameterModel, data: ProtocolSide): 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
import com.google.devtools.ksp.symbol.KSValueParameter
import com.google.devtools.ksp.visitor.KSEmptyVisitor
import com.squareup.kotlinpoet.ClassName
import de.justjanne.libquassel.generator.Constants
import de.justjanne.libquassel.generator.annotation.RpcFunctionAnnotation
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.util.ksp.asTypeName
class KSDeclarationParser(
class KotlinModelGenerator(
private val resolver: Resolver,
private val logger: KSPLogger
private val logger: KSPLogger,
) : KSEmptyVisitor<Unit, RpcModel?>() {
override fun visitClassDeclaration(
classDeclaration: KSClassDeclaration,
data: Unit
data: Unit,
): RpcModel.ObjectModel? {
val annotation = RpcObjectAnnotation.of(classDeclaration, resolver)
?: return null
val annotation =
RpcObjectAnnotation.of(classDeclaration, resolver)
?: return null
try {
return RpcModel.ObjectModel(
classDeclaration,
ClassName(
classDeclaration.packageName.asString(),
classDeclaration.simpleName.asString()
classDeclaration.simpleName.asString(),
),
annotation.name,
annotation.side,
classDeclaration.getDeclaredFunctions()
.mapNotNull { it.accept(this, Unit) }
.mapNotNull { it as? RpcModel.FunctionModel }
.toList()
.toList(),
)
} catch (t: Throwable) {
logger.error("Error processing ${annotation.name}", classDeclaration)
......@@ -55,19 +59,22 @@ class KSDeclarationParser(
override fun visitFunctionDeclaration(
function: KSFunctionDeclaration,
data: Unit
data: Unit,
): RpcModel.FunctionModel? {
val annotation = RpcFunctionAnnotation.of(function, resolver)
?: return null
val annotation =
RpcFunctionAnnotation.of(function, resolver)
?: return null
try {
val parameters = function.parameters
.mapNotNull { it.accept(this, Unit) }
.mapNotNull { it as? RpcModel.ParameterModel }
return RpcModel.FunctionModel(
function,
parameters.firstOrNull()?.type != Constants.TYPENAME_OBJECTNAME,
function.simpleName.asString(),
annotation.name,
annotation.target,
function.parameters
.mapNotNull { it.accept(this, Unit) }
.mapNotNull { it as? RpcModel.ParameterModel }
annotation.type,
parameters.filter { it.type != Constants.TYPENAME_OBJECTNAME },
)
} catch (t: Throwable) {
logger.error("Error processing ${annotation.name ?: function.simpleName.asString()}", function)
......@@ -78,13 +85,15 @@ class KSDeclarationParser(
override fun visitValueParameter(
valueParameter: KSValueParameter,
data: Unit
data: Unit,
): RpcModel.ParameterModel {
try {
val annotation = RpcParameterAnnotation.of(valueParameter)
return RpcModel.ParameterModel(
valueParameter,
valueParameter.name?.asString(),
valueParameter.type.asTypeName()
valueParameter.name!!.asString(),
valueParameter.type.asTypeName(),
annotation?.type,
)
} catch (t: Throwable) {
logger.error("Error processing ${valueParameter.name?.asString()}", valueParameter)
......@@ -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,20 +22,24 @@ class KotlinSaver : KotlinModelVisitor<CodeGenerator, Unit> {
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()) {
"Source may not be empty. Sources was empty for $model"
}
val file = try {
data.createNewFile(
generateDependencies(model.source),
model.data.packageName,
model.data.name
)
} catch (_: FileAlreadyExistsException) {
return
}
val file =
try {
data.createNewFile(
generateDependencies(model.source),
model.data.packageName,
model.data.name,
)
} catch (_: FileAlreadyExistsException) {
return
}
val writer = file.bufferedWriter(Charsets.UTF_8)
model.data.writeTo(writer)
try {
......@@ -47,6 +51,6 @@ class KotlinSaver : KotlinModelVisitor<CodeGenerator, Unit> {
override fun visitFunctionModel(
model: KotlinModel.FunctionModel,
data: CodeGenerator
data: CodeGenerator,
) = 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
import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor
class RpcObjectCollector : RpcModelVisitor<Unit, Unit> {
class RpcModelCollector : RpcModelVisitor<Unit> {
val objects = mutableListOf<RpcModel.ObjectModel>()
override fun visitObjectModel(model: RpcModel.ObjectModel, data: Unit) {
override fun visitObjectModel(
model: RpcModel.ObjectModel,
) {
objects.add(model)
}
override fun visitFunctionModel(model: RpcModel.FunctionModel, data: Unit) = Unit
override fun visitParameterModel(model: RpcModel.ParameterModel, data: Unit) = Unit
override fun visitFunctionModel(
model: RpcModel.FunctionModel,
) = Unit
override fun visitParameterModel(
model: RpcModel.ParameterModel,
) = Unit
}
......@@ -10,16 +10,19 @@
package de.justjanne.libquassel.irc
object HostmaskHelper {
fun nick(mask: String) = mask
.substringBeforeLast('@')
.substringBefore('!')
fun nick(mask: String) =
mask
.substringBeforeLast('@')
.substringBefore('!')
fun user(mask: String) = mask
.substringBeforeLast('@')
.substringAfter('!', missingDelimiterValue = "")
fun user(mask: String) =
mask
.substringBeforeLast('@')
.substringAfter('!', missingDelimiterValue = "")
fun host(mask: String) = mask
.substringAfterLast('@', missingDelimiterValue = "")
fun host(mask: String) =
mask
.substringAfterLast('@', missingDelimiterValue = "")
fun split(mask: String): Triple<String, String, String> {
val userPart = mask.substringBeforeLast('@')
......@@ -31,7 +34,11 @@ object HostmaskHelper {
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)
if (!user.isNullOrEmpty()) {
append("!$user")
......
......@@ -13,37 +13,46 @@ import java.util.Locale
abstract class IrcCaseMapper {
@JvmName("equalsIgnoreCaseNonNull")
fun equalsIgnoreCase(a: String?, b: String?) =
a == null && b == null || (a != null && b != null && equalsIgnoreCase(a, b))
fun equalsIgnoreCase(
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")
fun toLowerCase(value: String?): String? = value
?.let(this@IrcCaseMapper::toLowerCase)
fun toLowerCase(value: String?): String? =
value
?.let(this@IrcCaseMapper::toLowerCase)
abstract fun toLowerCase(value: String): String
@JvmName("toUpperCaseNonNull")
fun toUpperCase(value: String?): String? = value
?.let(this@IrcCaseMapper::toUpperCase)
fun toUpperCase(value: String?): String? =
value
?.let(this@IrcCaseMapper::toUpperCase)
abstract fun toUpperCase(value: String): String
object Unicode : IrcCaseMapper() {
override fun equalsIgnoreCase(a: String, b: String): Boolean =
a.equals(b, ignoreCase = true)
override fun equalsIgnoreCase(
a: String,
b: String,
): Boolean = a.equals(b, ignoreCase = true)
override fun toLowerCase(value: String): String =
value.lowercase(Locale.ROOT)
override fun toLowerCase(value: String): String = value.lowercase(Locale.ROOT)
override fun toUpperCase(value: String): String =
value.uppercase(Locale.ROOT)
override fun toUpperCase(value: String): String = value.uppercase(Locale.ROOT)
}
object Rfc1459 : IrcCaseMapper() {
override fun equalsIgnoreCase(a: String, b: String): Boolean =
toLowerCase(a) == toLowerCase(b) || toUpperCase(a) == toUpperCase(b)
override fun equalsIgnoreCase(
a: String,
b: String,
): Boolean = toLowerCase(a) == toLowerCase(b) || toUpperCase(a) == toUpperCase(b)
override fun toLowerCase(value: String): String =
value.lowercase(Locale.ROOT)
......@@ -60,7 +69,10 @@ abstract class IrcCaseMapper {
companion object {
operator fun get(caseMapping: String?) =
if (caseMapping.equals("rfc1459", ignoreCase = true)) Rfc1459
else Unicode
if (caseMapping.equals("rfc1459", ignoreCase = true)) {
Rfc1459
} else {
Unicode
}
}
}
......@@ -14,14 +14,15 @@ import de.justjanne.libquassel.irc.extensions.joinString
object IrcFormat {
data class Span(
val content: String,
val style: Style = Style()
val style: Style = Style(),
) {
override fun toString(): String = joinString(", ", "Info(", ")") {
append(content)
if (style != Style()) {
append("style=$style")
override fun toString(): String =
joinString(", ", "Info(", ")") {
append(content)
if (style != Style()) {
append("style=$style")
}
}
}
}
data class Style(
......@@ -29,21 +30,23 @@ object IrcFormat {
val foreground: Color? = null,
val background: Color? = null,
) {
fun flipFlag(flag: Flag) = copy(
flags = if (flags.contains(flag)) flags - flag else flags + flag
)
fun flipFlag(flag: Flag) =
copy(
flags = if (flags.contains(flag)) flags - flag else flags + flag,
)
override fun toString(): String = joinString(", ", "Info(", ")") {
if (flags.isNotEmpty()) {
append("flags=$flags")
}
if (foreground != null) {
append("foreground=$foreground")
override fun toString(): String =
joinString(", ", "Info(", ")") {
if (flags.isNotEmpty()) {
append("flags=$flags")
}
if (foreground != null) {
append("foreground=$foreground")
}
if (background != null) {
append("background=$background")
}
}
if (background != null) {
append("background=$background")
}
}
}
sealed class Color {
......@@ -62,6 +65,6 @@ object IrcFormat {
UNDERLINE,
STRIKETHROUGH,
MONOSPACE,
INVERSE
INVERSE,
}
}