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

Use Kotlin UI DSL #470

Merged
merged 21 commits into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 9 additions & 7 deletions .config/detekt/.detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ complexity:
# [False Positive] Solved using resource bundles. All remaining duplicates are resource identifiers.
StringLiteralDuplication:
active: false
# [Exception] Acceptable for helper files.
TooManyFunctions:
excludes: '**/*Helpers.kt'

formatting:
# [Disagree] Acceptable for many short arguments.
Expand All @@ -39,7 +42,7 @@ formatting:
# [Disagree] Consecutive blank lines are used consistently to group blocks of code.
NoConsecutiveBlankLines:
active: false
# [Disagree] Unnecessary, except for calls with vararg.
# [Disagree] Required for lists and varargs, but ugly when it is unlikely that the function signature will change.
TrailingCommaOnCallSite:
active: false

Expand All @@ -52,9 +55,12 @@ style:
# [Disagree] `apply` is confusing to use because of namespace conflicts.
AlsoCouldBeApply:
active: false
# [Disagree] Braces use unnecessary extra space.
# [Disagree] Braces use unnecessary extra space, but consistency helps in legibility.
BracesOnIfStatements:
multiLine: consistent
# [Disagree] Legibility is possible despite consistency because it is wrapped in a multi-line `when` block.
BracesOnWhenStatements:
multiLine: necessary
# [Disagree] Functions on data classes are useful.
DataClassContainsFunctions:
active: false
Expand All @@ -63,8 +69,7 @@ style:
active: false
# [Exception] Acceptable in (parameterized) tests.
DestructuringDeclarationWithTooManyEntries:
excludes:
- '**/test/**'
excludes: '**/test/**'
# [Bug] Fails when used as function expression body.
MultilineRawStringIndentation:
active: false
Expand All @@ -74,6 +79,3 @@ style:
# [Bug] Suggests using raw string in constant, but then `trimIndent` is not possible, resulting in weird indenting.
StringShouldBeRawString:
active: false
# [Exception] Used by scene builder.
UnusedPrivateMember:
allowedNames: "createUIComponents"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ See [Plugin Signing](https://plugins.jetbrains.com/docs/intellij/plugin-signing.
### 🧪 Quality assurance
```bash
$ gradlew test # Run tests
$ gradlew test --tests X # Run tests in class X
$ gradlew test --tests X # Run tests in class X (package name optional)
$ gradlew check # Run tests and static analysis
$ gradlew runPluginVerifier # Check for compatibility issues
```
Expand Down
10 changes: 7 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ plugins {

// Documentation
id("org.jetbrains.changelog") version "2.1.0"
id("org.jetbrains.dokka") version "1.8.20" // See also `gradle.properties
id("org.jetbrains.dokka") version "1.8.20"
}


Expand All @@ -37,11 +37,13 @@ dependencies {
implementation("com.vdurmont:emoji-java:${properties("emojiVersion")}")
api("org.jetbrains.kotlin:kotlin-reflect")

testImplementation("org.assertj:assertj-core:${properties("assertjVersion")}")
testImplementation("org.assertj:assertj-swing-junit:${properties("assertjSwingVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-runner:${properties("junitRunnerVersion")}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${properties("junitVersion")}")
testImplementation("org.junit.jupiter:junit-jupiter-engine:${properties("junitVersion")}")
testImplementation("org.junit.vintage:junit-vintage-engine:${properties("junitVersion")}")
testImplementation("io.kotest:kotest-assertions-core:${properties("kotestVersion")}")
testImplementation("io.kotest:kotest-framework-datatest:${properties("kotestVersion")}")
testImplementation("io.kotest:kotest-runner-junit5:${properties("kotestVersion")}")

detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${properties("detektVersion")}")
Expand Down Expand Up @@ -134,10 +136,12 @@ tasks {

// Documentation
dokkaHtml.configure {
notCompatibleWithConfigurationCache("Not sure why")

pluginsMapConfiguration.set(
mapOf(
"org.jetbrains.dokka.base.DokkaBase" to
"""{ "footerMessage": "© ${Year.now().value} Florine W. Dekker" }"""
"""{ "footerMessage": "© ${Year.now().value} Florine W. Dekker" }"""
)
)
moduleName.set("Randomness v${properties("version")}")
Expand Down
6 changes: 2 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group=com.fwdekker
# TODO: Also remove `beta` from `TemplateSettings` storage file!
# TODO: Also remove `beta` from `PersistentSettings' annotation`!
version=3.0.0-beta.3

# Compatibility
Expand Down Expand Up @@ -28,8 +28,6 @@ kotlinApiVersion=1.6
# Dependencies
# * Detekt should also be updated in `plugins` block.
# * RgxGen should also be updated in `StringSchemeEditor` link.
# * Check releases at https://github.com/assertj/assertj-swing/ for valid AssertJ version combinations.
assertjVersion=3.17.2
assertjSwingVersion=3.17.1
dateparserVersion=1.0.11
detektVersion=1.23.0
Expand All @@ -47,5 +45,5 @@ org.gradle.configuration-cache=true
# Kotlin
kotlin.code.style=official
kotlin.stdlib.default.dependency=false
# TODO: Workaround for https://jb.gg/intellij-platform-kotlin-oom
# TODO: Workaround for https://jb.gg/intellij-platform-kotlin-oom, will not be necessary as of Kotlin 1.9.0
kotlin.incremental.useClasspathSnapshot=false
4 changes: 4 additions & 0 deletions packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Entry point of the plugin. Contains main classes, shared classes, and helper classes.

# Package com.fwdekker.randomness.affix

Decorator for surrounding a value with constant strings such as quotation marks and braces.

# Package com.fwdekker.randomness.array

Decorator for generating multiple values each time.
Expand Down
28 changes: 9 additions & 19 deletions src/main/kotlin/com/fwdekker/randomness/Box.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,19 @@ package com.fwdekker.randomness


/**
* Basically a reference, but the value is not instantiated until it is first read.
* A lazily-instantiated reference to an object of type [T].
*
* @param T the type of value referred to by this box
* @property generator Returns the instance of [T] that should be returned by [get] if [set] has not been called before.
* @property value Do not assign this field in the constructor. Placed in constructor to ensure Kotlin includes it in
* the automatically generated [copy] method.
* @param T the type of the referenced object
* @property generator Generates the referenced object when [unaryPlus] is invoked for the first time.
* @property value Do not assign this field in the constructor; this field is placed in the constructor to ensure Kotlin
* includes it in the automatically-generated [copy] method.
*/
data class Box<T>(private val generator: () -> T, private var value: T? = null) {
data class Box<T : Any>(private val generator: () -> T, private var value: T? = null) {
/**
* Returns the value set by [set], or returns the value previously returned by [get], or returns a value created by
* [generator].
* If this method is invoked for the first time, [generator] is invoked and the result is returned. In subsequent
* invocations of this method, the previously-generated value is returned each time.
*
* @return the value set by [set], or returns the value previously returned by [get], or returns a value created by
* [generator]
* @return the referenced value
*/
operator fun unaryPlus(): T = value ?: generator().also { value = it }

/**
* Replaces the referred-to value with [value] so that the next call to [get] returns [value].
*
* @param value the value to write into the box
*/
operator fun plusAssign(value: T) {
this.value = value
}
}
47 changes: 0 additions & 47 deletions src/main/kotlin/com/fwdekker/randomness/BracketsDescriptor.kt

This file was deleted.

36 changes: 26 additions & 10 deletions src/main/kotlin/com/fwdekker/randomness/Bundle.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fwdekker.randomness

import java.util.MissingResourceException
import java.util.ResourceBundle


Expand All @@ -16,21 +17,36 @@ object Bundle {
/**
* Returns the string at [key].
*
* Throws an exception if no string with [key] can be found.
*
* @param key the key of the string to return
* @return the string at [key]
* @throws MissingResourceException if no string with [key] can be found
*/
@Throws(MissingResourceException::class)
operator fun invoke(key: String): String = RESOURCE_BUNDLE.getString(key)

/**
* Returns the string at [key] formatted with [arguments].
*
* Throws an exception if no string with [key] can be found.
*
* @param key the key of the string to return
* @param arguments the arguments to insert into the template
* @return the string at [key] formatted with [arguments]
* @throws MissingResourceException if no string with [key] can be found
*/
operator fun invoke(key: String, vararg arguments: Any?): String = RESOURCE_BUNDLE.getString(key).format(*arguments)
@Throws(MissingResourceException::class)
operator fun invoke(key: String, vararg arguments: Any?): String = this(key).format(*arguments)
}


/**
* Returns `true` if [format] is a format string for `this` string, optionally after inserting [args] into [format].
*
* @throws java.util.MissingFormatArgumentException if [args] has fewer arguments than required for [format]
*/
fun String.matchesFormat(format: String, vararg args: String) =
Regex("%[0-9]+\\\$[Ss]").findAll(format)
.toList()
.reversed()
.fold(format) { acc, match ->
if (match.value.drop(1).dropLast(2).toInt() > args.size)
acc.replaceRange(match.range, ".*")
else
acc
}
.format(*args)
.let { Regex(it) }
.matches(this)
61 changes: 12 additions & 49 deletions src/main/kotlin/com/fwdekker/randomness/CapitalizationMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,95 +7,58 @@ import kotlin.random.Random
/**
* A mode in which a word should be capitalized.
*
* @property descriptor The name of the capitalization mode.
* @property transformer The function which capitalizes the given string to the mode's format.
* @property transform The function which capitalizes the given string to the mode's format.
*/
enum class CapitalizationMode(val descriptor: String, private val transformer: (String, Random) -> String) {
enum class CapitalizationMode(val transform: (String, Random) -> String) {
/**
* Does not change the string.
*/
RETAIN("retain", { string, _ -> string }),
RETAIN({ string, _ -> string }),

/**
* Makes the first character uppercase and all characters after that lowercase.
*/
SENTENCE("sentence", { string, _ -> string.toSentenceCase() }),
SENTENCE({ string, _ -> string.toSentenceCase() }),

/**
* Makes all characters uppercase.
*/
UPPER("upper", { string, _ -> string.uppercase(Locale.getDefault()) }),
UPPER({ string, _ -> string.uppercase(Locale.getDefault()) }),

/**
* Makes all characters lowercase.
*/
LOWER("lower", { string, _ -> string.lowercase(Locale.getDefault()) }),
LOWER({ string, _ -> string.lowercase(Locale.getDefault()) }),

/**
* Makes the first letter of each word uppercase.
*/
FIRST_LETTER("first letter", { string, _ -> string.split(' ').joinToString(" ") { it.toSentenceCase() } }),
FIRST_LETTER({ string, _ -> string.split(' ').joinToString(" ") { it.toSentenceCase() } }),

/**
* Makes each letter randomly uppercase or lowercase.
*/
RANDOM("random", { string, random -> string.toCharArray().map { it.toRandomCase(random) }.joinToString("") }),

/**
* Unused in production code.
*/
DUMMY("dummy", { string, _ -> string }),
RANDOM({ string, random -> string.toCharArray().map { it.toRandomCase(random) }.joinToString("") }),
;


/**
* Invokes [transformer] with [random].
*
* @param string the string to transform
* @param random the random instance to use for transforming
* @return the returned value of [transformer]
*/
fun transform(string: String, random: Random = Random.Default) = transformer(string, random)

/**
* Returns the [descriptor] of the capitalization mode.
*
* @return the [descriptor] of the capitalization mode
*/
override fun toString() = descriptor


/**
* Holds static elements.
* Returns the localized string name of this mode.
*/
companion object {
/**
* Returns the capitalization mode corresponding to [descriptor].
*
* @param descriptor the descriptor of the capitalization mode to return
* @return the capitalization mode corresponding to [descriptor]
*/
fun getMode(descriptor: String) =
values().firstOrNull { it.descriptor == descriptor }
?: throw IllegalArgumentException(Bundle("shared.capitalization.error.name_not_found", descriptor))
}
fun toLocalizedString() =
Bundle("shared.capitalization.${toString().replace(' ', '_').lowercase(Locale.getDefault())}")
}


/**
* Randomly converts this character to uppercase or lowercase.
*
* @param random the source of randomness to use
* @return the uppercase or lowercase version of this character
* Randomly converts this character to uppercase or lowercase using [random] as a source of randomness.
*/
private fun Char.toRandomCase(random: Random) =
if (random.nextBoolean()) this.lowercaseChar()
else this.uppercaseChar()

/**
* Turns the first character uppercase while all other characters become lowercase.
*
* @return the sentence-case form of this string
*/
private fun String.toSentenceCase() =
this.lowercase(Locale.getDefault()).replaceFirstChar { it.uppercaseChar() }
Loading