|
|
|
@ -2,6 +2,8 @@ package org.jetbrains.compose.resources
|
|
|
|
|
|
|
|
|
|
import com.squareup.kotlinpoet.* |
|
|
|
|
import java.nio.file.Path |
|
|
|
|
import java.util.SortedMap |
|
|
|
|
import java.util.TreeMap |
|
|
|
|
import kotlin.io.path.invariantSeparatorsPathString |
|
|
|
|
|
|
|
|
|
internal enum class ResourceType(val typeName: String) { |
|
|
|
@ -26,12 +28,14 @@ internal data class ResourceItem(
|
|
|
|
|
val path: Path |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
private fun ResourceItem.getClassName(): ClassName = when (type) { |
|
|
|
|
private fun ResourceType.getClassName(): ClassName = when (this) { |
|
|
|
|
ResourceType.DRAWABLE -> ClassName("org.jetbrains.compose.resources", "DrawableResource") |
|
|
|
|
ResourceType.STRING -> ClassName("org.jetbrains.compose.resources", "StringResource") |
|
|
|
|
ResourceType.FONT -> ClassName("org.jetbrains.compose.resources", "FontResource") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private val resourceItemClass = ClassName("org.jetbrains.compose.resources", "ResourceItem") |
|
|
|
|
|
|
|
|
|
private fun CodeBlock.Builder.addQualifiers(resourceItem: ResourceItem): CodeBlock.Builder { |
|
|
|
|
val languageQualifier = ClassName("org.jetbrains.compose.resources", "LanguageQualifier") |
|
|
|
|
val regionQualifier = ClassName("org.jetbrains.compose.resources", "RegionQualifier") |
|
|
|
@ -101,85 +105,118 @@ internal fun getResFileSpec(
|
|
|
|
|
//type -> id -> items |
|
|
|
|
resources: Map<ResourceType, Map<String, List<ResourceItem>>>, |
|
|
|
|
packageName: String |
|
|
|
|
): FileSpec = FileSpec.builder(packageName, "Res").apply { |
|
|
|
|
addType(TypeSpec.objectBuilder("Res").apply { |
|
|
|
|
addModifiers(KModifier.INTERNAL) |
|
|
|
|
): FileSpec = |
|
|
|
|
FileSpec.builder(packageName, "Res").apply { |
|
|
|
|
addAnnotation( |
|
|
|
|
AnnotationSpec.builder(ClassName("kotlin", "OptIn")) |
|
|
|
|
.addMember("org.jetbrains.compose.resources.InternalResourceApi::class") |
|
|
|
|
.build() |
|
|
|
|
) |
|
|
|
|
addAnnotation( |
|
|
|
|
AnnotationSpec.builder(ClassName("org.jetbrains.compose.resources", "ExperimentalResourceApi")) |
|
|
|
|
.addMember("org.jetbrains.compose.resources.ExperimentalResourceApi::class") |
|
|
|
|
.build() |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
//readFileBytes |
|
|
|
|
val readResourceBytes = MemberName("org.jetbrains.compose.resources", "readResourceBytes") |
|
|
|
|
addFunction( |
|
|
|
|
FunSpec.builder("readBytes") |
|
|
|
|
.addKdoc(""" |
|
|
|
|
//we need to sort it to generate the same code on different platforms |
|
|
|
|
val sortedResources = sortResources(resources) |
|
|
|
|
|
|
|
|
|
addType(TypeSpec.objectBuilder("Res").apply { |
|
|
|
|
addModifiers(KModifier.INTERNAL) |
|
|
|
|
addAnnotation( |
|
|
|
|
AnnotationSpec.builder( |
|
|
|
|
ClassName("org.jetbrains.compose.resources", "ExperimentalResourceApi") |
|
|
|
|
).build() |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
//readFileBytes |
|
|
|
|
val readResourceBytes = MemberName("org.jetbrains.compose.resources", "readResourceBytes") |
|
|
|
|
addFunction( |
|
|
|
|
FunSpec.builder("readBytes") |
|
|
|
|
.addKdoc( |
|
|
|
|
""" |
|
|
|
|
Reads the content of the resource file at the specified path and returns it as a byte array. |
|
|
|
|
|
|
|
|
|
Example: `val bytes = Res.readBytes("files/key.bin")` |
|
|
|
|
|
|
|
|
|
@param path The path of the file to read in the compose resource's directory. |
|
|
|
|
@return The content of the file as a byte array. |
|
|
|
|
""".trimIndent()) |
|
|
|
|
.addParameter("path", String::class) |
|
|
|
|
.addModifiers(KModifier.SUSPEND) |
|
|
|
|
.returns(ByteArray::class) |
|
|
|
|
.addStatement("return %M(path)", readResourceBytes) //todo: add module ID here |
|
|
|
|
.build() |
|
|
|
|
) |
|
|
|
|
""".trimIndent() |
|
|
|
|
) |
|
|
|
|
.addParameter("path", String::class) |
|
|
|
|
.addModifiers(KModifier.SUSPEND) |
|
|
|
|
.returns(ByteArray::class) |
|
|
|
|
.addStatement("return %M(path)", readResourceBytes) //todo: add module ID here |
|
|
|
|
.build() |
|
|
|
|
) |
|
|
|
|
val types = sortedResources.map { (type, idToResources) -> |
|
|
|
|
getResourceTypeObject(type, idToResources) |
|
|
|
|
} |
|
|
|
|
addTypes(types) |
|
|
|
|
}.build()) |
|
|
|
|
|
|
|
|
|
val types = resources.map { (type, idToResources) -> |
|
|
|
|
getResourceTypeObject(type, idToResources) |
|
|
|
|
}.sortedBy { it.name } |
|
|
|
|
addTypes(types) |
|
|
|
|
}.build()) |
|
|
|
|
}.build() |
|
|
|
|
sortedResources |
|
|
|
|
.flatMap { (type, idToResources) -> |
|
|
|
|
idToResources.map { (name, items) -> |
|
|
|
|
getResourceInitializer(name, type, items) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.forEach { addFunction(it) } |
|
|
|
|
}.build() |
|
|
|
|
|
|
|
|
|
private fun getResourceTypeObject(type: ResourceType, nameToResources: Map<String, List<ResourceItem>>) = |
|
|
|
|
TypeSpec.objectBuilder(type.typeName).apply { |
|
|
|
|
nameToResources.entries |
|
|
|
|
.sortedBy { it.key } |
|
|
|
|
.forEach { (name, items) -> |
|
|
|
|
addResourceProperty(name, items.sortedBy { it.path }) |
|
|
|
|
nameToResources.keys |
|
|
|
|
.forEach { name -> |
|
|
|
|
addProperty( |
|
|
|
|
PropertySpec |
|
|
|
|
.builder(name, type.getClassName()) |
|
|
|
|
.initializer("get_$name()") |
|
|
|
|
.build() |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
}.build() |
|
|
|
|
|
|
|
|
|
private fun TypeSpec.Builder.addResourceProperty(name: String, items: List<ResourceItem>) { |
|
|
|
|
val resourceItemClass = ClassName("org.jetbrains.compose.resources", "ResourceItem") |
|
|
|
|
|
|
|
|
|
val first = items.first() |
|
|
|
|
val propertyClassName = first.getClassName() |
|
|
|
|
val resourceId = first.let { "${it.type}:${it.name}" } |
|
|
|
|
|
|
|
|
|
val initializer = CodeBlock.builder() |
|
|
|
|
.add("%T(\n", propertyClassName).withIndent { |
|
|
|
|
add("\"$resourceId\",\n") |
|
|
|
|
if (first.type == ResourceType.STRING) { |
|
|
|
|
add("\"${first.name}\",\n") |
|
|
|
|
} |
|
|
|
|
add("setOf(\n").withIndent { |
|
|
|
|
items.forEach { item -> |
|
|
|
|
add("%T(\n", resourceItemClass).withIndent { |
|
|
|
|
add("setOf(").addQualifiers(item).add("),\n") |
|
|
|
|
//file separator should be '/' on all platforms |
|
|
|
|
add("\"${item.path.invariantSeparatorsPathString}\"\n") //todo: add module ID here |
|
|
|
|
private fun getResourceInitializer(name: String, type: ResourceType, items: List<ResourceItem>): FunSpec { |
|
|
|
|
val propertyTypeName = type.getClassName() |
|
|
|
|
val resourceId = "${type}:${name}" |
|
|
|
|
return FunSpec.builder("get_$name") |
|
|
|
|
.addModifiers(KModifier.PRIVATE) |
|
|
|
|
.returns(propertyTypeName) |
|
|
|
|
.addStatement( |
|
|
|
|
CodeBlock.builder() |
|
|
|
|
.add("return %T(\n", propertyTypeName).withIndent { |
|
|
|
|
add("\"$resourceId\",") |
|
|
|
|
if (type == ResourceType.STRING) add(" \"$name\",") |
|
|
|
|
withIndent { |
|
|
|
|
add("\nsetOf(\n").withIndent { |
|
|
|
|
items.forEach { item -> |
|
|
|
|
add("%T(", resourceItemClass) |
|
|
|
|
add("setOf(").addQualifiers(item).add("), ") |
|
|
|
|
//file separator should be '/' on all platforms |
|
|
|
|
add("\"${item.path.invariantSeparatorsPathString}\"") //todo: add module ID here |
|
|
|
|
add("),\n") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
add(")\n") |
|
|
|
|
} |
|
|
|
|
add("),\n") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
add(")\n") |
|
|
|
|
} |
|
|
|
|
.add(")") |
|
|
|
|
.add(")") |
|
|
|
|
.build().toString() |
|
|
|
|
) |
|
|
|
|
.build() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
addProperty( |
|
|
|
|
PropertySpec.builder(name, propertyClassName) |
|
|
|
|
.initializer(initializer) |
|
|
|
|
.build() |
|
|
|
|
) |
|
|
|
|
private fun sortResources( |
|
|
|
|
resources: Map<ResourceType, Map<String, List<ResourceItem>>> |
|
|
|
|
): TreeMap<ResourceType, TreeMap<String, List<ResourceItem>>> { |
|
|
|
|
val result = TreeMap<ResourceType, TreeMap<String, List<ResourceItem>>>() |
|
|
|
|
resources |
|
|
|
|
.entries |
|
|
|
|
.forEach { (type, items) -> |
|
|
|
|
val typeResult = TreeMap<String, List<ResourceItem>>() |
|
|
|
|
items |
|
|
|
|
.entries |
|
|
|
|
.forEach { (name, resItems) -> |
|
|
|
|
typeResult[name] = resItems.sortedBy { it.path } |
|
|
|
|
} |
|
|
|
|
result[type] = typeResult |
|
|
|
|
} |
|
|
|
|
return result |
|
|
|
|
} |