Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New plugin demo #163

Merged
merged 24 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ gradlePlugin {
id = "intellij-theme-generator"
implementationClass = "org.jetbrains.jewel.buildlogic.theme.IntelliJThemeGeneratorPlugin"
}
register("android-studio-releases-generator") {
id = "android-studio-releases-generator"
implementationClass = "org.jetbrains.jewel.buildlogic.demodata.AndroidStudioReleasesGeneratorPlugin"
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package org.jetbrains.jewel.buildlogic.demodata

import com.squareup.kotlinpoet.ClassName
import gradle.kotlin.dsl.accessors._c011fd04eb69b06af6f445fec200c5f6.main
import gradle.kotlin.dsl.accessors._c011fd04eb69b06af6f445fec200c5f6.sourceSets
import io.gitlab.arturbosch.detekt.Detekt
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.property
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.setProperty
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile
import java.io.File
import java.net.URL

abstract class AndroidStudioReleasesGeneratorPlugin : Plugin<Project> {

final override fun apply(target: Project) {
with(target) {
val extension = extensions.findByType(StudioVersionsGenerationExtension::class.java)
?: extensions.create("androidStudioReleasesGenerator", StudioVersionsGenerationExtension::class.java)

val task =
tasks.register<AndroidStudioReleasesGeneratorTask>("generateAndroidStudioReleasesList") {
val className = ClassName.bestGuess(OUTPUT_CLASS_NAME)
outputFile.set(
extension.targetDir.file(
className.packageName.replace(".", "/")
.plus("/${className.simpleName}.kt")
)
)
dataUrl.set(extension.dataUrl)
resourcesDirs.set(extension.resourcesDirs)
}
tasks.withType<BaseKotlinCompile> {
dependsOn(task)
}
tasks.withType<Detekt> {
dependsOn(task)
}
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
extensions.getByType<KotlinJvmProjectExtension>().apply {
sourceSets["main"].kotlin.srcDir(extension.targetDir)
}
}
}
}
}

open class StudioVersionsGenerationExtension(project: Project) {

val targetDir: DirectoryProperty = project.objects.directoryProperty()
.convention(project.layout.buildDirectory.dir("generated/studio-releases/"))

val resourcesDirs: SetProperty<File> = project.objects.setProperty<File>()
.convention(project.sourceSets.main.get().resources.srcDirs)

val dataUrl: Property<String> = project.objects.property<String>()
.convention("https://jb.gg/android-studio-releases-list.json")
}

private const val OUTPUT_CLASS_NAME = "org.jetbrains.jewel.samples.ideplugin.releasessample.AndroidStudioReleases"

open class AndroidStudioReleasesGeneratorTask : DefaultTask() {

@get:OutputFile
val outputFile: RegularFileProperty = project.objects.fileProperty()

@get:Input
val dataUrl = project.objects.property<String>()

@get:Input
val resourcesDirs = project.objects.setProperty<File>()

init {
group = "jewel"
}

@TaskAction
fun generate() {
val json = Json {
ignoreUnknownKeys = true
isLenient = true
}
val url = dataUrl.get()
val lookupDirs = resourcesDirs.get()

logger.lifecycle("Fetching Android Studio releases list from $url...")
logger.debug(
"Registered resources directories:\n" +
lookupDirs.joinToString("\n") { " * ${it.absolutePath}" }
)
val releases = URL(url).openStream()
.use { json.decodeFromStream<ApiAndroidStudioReleases>(it) }

val className = ClassName.bestGuess(OUTPUT_CLASS_NAME)
val file = AndroidStudioReleasesReader.readFrom(releases, className, url, lookupDirs)

val outputFile = outputFile.get().asFile
outputFile.bufferedWriter().use { file.writeTo(it) }
logger.lifecycle("Android Studio releases from $url parsed and code generated into ${outputFile.path}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package org.jetbrains.jewel.buildlogic.demodata

import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.joinToCode
import java.io.File
import java.time.ZonedDateTime

private val ContentItemClassName =
ClassName.bestGuess("org.jetbrains.jewel.samples.ideplugin.releasessample.ContentItem.AndroidStudio")

internal object AndroidStudioReleasesReader {

fun readFrom(
releases: ApiAndroidStudioReleases,
className: ClassName,
url: String,
resourceDirs: Set<File>,
) =
FileSpec.builder(className).apply {
indent(" ")
addFileComment("Generated by the Jewel Android Studio Releases Generator\n")
addFileComment("Generated from $url on ${ZonedDateTime.now()}\n")

addImport("org.jetbrains.jewel.samples.ideplugin.releasessample", "ContentItem.AndroidStudio")
addImport("kotlinx.datetime", "LocalDate")

addType(
TypeSpec.objectBuilder(className)
.superclass(
ClassName.bestGuess("org.jetbrains.jewel.samples.ideplugin.releasessample.ContentSource")
.parameterizedBy(ContentItemClassName)
)
.apply {
addProperty(
PropertySpec.builder(
name = "items",
type = List::class.asClassName().parameterizedBy(ContentItemClassName),
KModifier.OVERRIDE
)
.initializer(readReleases(releases, resourceDirs))
.build()
)

addProperty(
PropertySpec.builder(
"displayName",
type = String::class.asClassName(),
KModifier.OVERRIDE
)
.initializer("\"%L\"", "Android Studio releases")
.build()
)
}.build()
)
}.build()

private fun readReleases(releases: ApiAndroidStudioReleases, resourceDirs: Set<File>) =
releases.content.item
.map { readRelease(it, resourceDirs) }
.joinToCode(prefix = "listOf(\n", separator = ",\n", suffix = ")")

private fun readRelease(release: ApiAndroidStudioReleases.Content.Item, resourceDirs: Set<File>) =
CodeBlock.builder()
.apply {
add("AndroidStudio(\n")
add(" displayText = \"%L\",\n", release.name)
add(" imagePath = %L,\n", imagePathForOrNull(release, resourceDirs))
add(" versionName = \"%L\",\n", release.version)
add(" build = \"%L\",\n", release.build)
add(" platformBuild = \"%L\",\n", release.platformBuild)
add(" platformVersion = \"%L\",\n", release.platformVersion)
add(" channel = %L,\n", readChannel(release.channel))
add(" releaseDate = LocalDate(%L),\n", translateDate(release.date))
add(" key = \"%L\",\n", release.build)
add(")")
}
.build()

private fun imagePathForOrNull(release: ApiAndroidStudioReleases.Content.Item, resourceDirs: Set<File>): String? {
// Take the release animal from the name, remove spaces and voila'
val releaseAnimal = release.name
.substringBefore(" | ")
.substringAfter("Android Studio")
.trim()
.replace(" ", "")

if (releaseAnimal.isEmpty() || releaseAnimal.any { it.isDigit() }) return null

// We only have stable and canary splash screens. Betas use the stable ones.
val channel = release.channel.lowercase()
.let {
when (it) {
"release", "rc", "stable", "beta", "patch" -> "stable"
"canary", "preview", "alpha" -> "canary"
else -> {
println(" Note: channel '${it}' isn't supported for splash screens")
null
}
}
} ?: return null

val splashPath = "/studio-splash-screens/$releaseAnimal-$channel.png"
val splashFiles = resourceDirs.map { dir -> File(dir, splashPath) }
if (splashFiles.none { it.isFile }) {
println(" Note: expected splash screen file doesn't exist: '${splashPath}'")
return null
}

return "\"$splashPath\""
}

// This is the laziest crap ever, I am sorry.
private fun translateDate(rawDate: String): String {
val month = rawDate.substringBefore(" ").trimStart('0')
val year = rawDate.substringAfterLast(" ".trimStart('0'))
val day = rawDate.substring(month.length + 1, rawDate.length - year.length - 1).trimStart('0')

if (day.isEmpty()) {
println("$rawDate\nMonth: '$month'\nYear: '$year'")
}

val monthNumber = when (month.trim().lowercase()) {
"january" -> 1
"february" -> 2
"march" -> 3
"april" -> 4
"may" -> 5
"june" -> 6
"july" -> 7
"august" -> 8
"september" -> 9
"october" -> 10
"november" -> 11
"december" -> 12
else -> error("Unrecognized month: $month")
}

return "$year, $monthNumber, $day"
}

private fun readChannel(rawChannel: String) =
when (rawChannel.lowercase().trim()) {
"stable", "patch", "release" -> "ReleaseChannel.Stable"
"beta" -> "ReleaseChannel.Beta"
"canary" -> "ReleaseChannel.Canary"
else -> "ReleaseChannel.Other"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.jetbrains.jewel.buildlogic.demodata

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class ApiAndroidStudioReleases(
@SerialName("content")
val content: Content = Content()
) {

@Serializable
internal data class Content(
@SerialName("item")
val item: List<Item> = listOf(),
@SerialName("version")
val version: Int = 0
) {

@Serializable
internal data class Item(
@SerialName("build")
val build: String,
@SerialName("channel")
val channel: String,
@SerialName("date")
val date: String,
@SerialName("download")
val download: List<Download> = listOf(),
@SerialName("name")
val name: String,
@SerialName("platformBuild")
val platformBuild: String,
@SerialName("platformVersion")
val platformVersion: String,
@SerialName("version")
val version: String
) {

@Serializable
internal data class Download(
@SerialName("checksum")
val checksum: String,
@SerialName("link")
val link: String,
@SerialName("size")
val size: String
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal object IntUiThemeDescriptorReader {
) =
FileSpec.builder(className).apply {
indent(" ")
this.addFileComment("Generated by the Jewel Int UI Palette Generator\n")
addFileComment("Generated by the Jewel Int UI Palette Generator\n")
addFileComment("Generated from the IntelliJ Platform version $ideaVersion\n")
addFileComment("Source: $descriptorUrl")

Expand Down
Loading