Skip to content
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
47 changes: 36 additions & 11 deletions src/main/kotlin/com/fwdekker/randomness/array/ArrayDecorator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.intellij.util.xmlb.annotations.OptionTag
* @property maxCount The maximum number of elements to generate, inclusive.
* @property separatorEnabled Whether to separate elements using [separator].
* @property separator The string to place between generated elements.
* @property elementFormat The format according to which each element of the array should be formatted.
* @property affixDecorator The affixation to apply to the generated values.
*/
data class ArrayDecorator(
Expand All @@ -23,6 +24,7 @@ data class ArrayDecorator(
var maxCount: Int = DEFAULT_MAX_COUNT,
var separatorEnabled: Boolean = DEFAULT_SEPARATOR_ENABLED,
var separator: String = DEFAULT_SEPARATOR,
var elementFormat: String = DEFAULT_ELEMENT_FORMAT,
@OptionTag val affixDecorator: AffixDecorator = DEFAULT_AFFIX_DECORATOR,
) : DecoratorScheme() {
override val name = Bundle("array.title")
Expand All @@ -32,17 +34,24 @@ data class ArrayDecorator(


override fun generateUndecoratedStrings(count: Int): List<String> {
val partsPerString = List(count) { random.nextInt(minCount, maxCount + 1) }
val parts = generator(partsPerString.sum())

return partsPerString
.fold(Pair(parts, emptyList<String>())) { (remainingParts, createdStrings), nextPartCount ->
val nextString =
remainingParts
.take(nextPartCount)
.joinToString(if (separatorEnabled) separator.replace("\\n", "\n") else "")

Pair(remainingParts.drop(nextPartCount), createdStrings + nextString)
// Generates `count` arrays, with each array containing some number of elements.

val elementsPerArray = List(count) { random.nextInt(minCount, maxCount + 1) }
val elements = generator(elementsPerArray.sum())

return elementsPerArray
.foldIndexed(Pair(elements, emptyList<String>())) { aId, (remainingElements, createdArrays), elementCount ->
val newArray = remainingElements
.take(elementCount)
.mapIndexed { eId, element ->
elementFormat
.replace("{aid}", aId.toString())
.replace("{eid}", eId.toString())
.replace("{val}", element) // Must be last, to avoid recursive replacement
}
.joinToString(separator = if (separatorEnabled) separator.replace("\\n", "\n") else "")

Pair(remainingElements.drop(elementCount), createdArrays + newArray)
}
.second
}
Expand Down Expand Up @@ -95,6 +104,22 @@ data class ArrayDecorator(
*/
const val DEFAULT_SEPARATOR = ", "

/**
* The preset values for the [format] field.
*/
val PRESET_ELEMENT_FORMATS = listOf(
"{val}",
"{eid}: {val}",
"{eid}={val}",
"({aid}, {eid}): {val}",
"({aid}, {eid})={val}",
)

/**
* The default value of the [format] field.
*/
const val DEFAULT_ELEMENT_FORMAT = "{val}"

/**
* The preset values for the [affixDecorator] descriptor.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.fwdekker.randomness.SchemeEditor
import com.fwdekker.randomness.affix.AffixDecoratorEditor
import com.fwdekker.randomness.array.ArrayDecorator.Companion.MIN_MIN_COUNT
import com.fwdekker.randomness.array.ArrayDecorator.Companion.PRESET_AFFIX_DECORATOR_DESCRIPTORS
import com.fwdekker.randomness.array.ArrayDecorator.Companion.PRESET_ELEMENT_FORMATS
import com.fwdekker.randomness.array.ArrayDecorator.Companion.PRESET_SEPARATORS
import com.fwdekker.randomness.ui.JIntSpinner
import com.fwdekker.randomness.ui.UIConstants
Expand All @@ -18,7 +19,6 @@ import com.fwdekker.randomness.ui.ofConstant
import com.fwdekker.randomness.ui.withFixedWidth
import com.fwdekker.randomness.ui.withName
import com.intellij.ui.dsl.builder.BottomGap
import com.intellij.ui.dsl.builder.Cell
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.builder.selected
Expand All @@ -43,16 +43,14 @@ class ArrayDecoratorEditor(
) : SchemeEditor<ArrayDecorator>(scheme) {
override val rootComponent = panel {
decoratedRowRange(title = if (!embedded) Bundle("array.title") else null, indent = !embedded) {
lateinit var enabledCheckBox: Cell<JCheckBox>
lateinit var isEnabled: ComponentPredicate

row {
checkBox(Bundle("array.ui.enabled"))
.loadMnemonic()
.withName("arrayEnabled")
.bindSelected(scheme::enabled)
.also { enabledCheckBox = it }
.also { isEnabled = enabledCheckBox.selected.or(ComponentPredicate.ofConstant(embedded)) }
.also { isEnabled = it.selected.or(ComponentPredicate.ofConstant(embedded)) }
}.visible(!embedded)

decoratedRowRange(indent = !embedded) {
Expand Down Expand Up @@ -92,6 +90,14 @@ class ArrayDecoratorEditor(
.bindCurrentText(scheme::separator)
}

row(Bundle("array.ui.element_format.option")) {
comboBox(PRESET_ELEMENT_FORMATS)
.isEditable(true)
.withName("arrayElementFormat")
.bindCurrentText(scheme::elementFormat)
contextHelp(Bundle("array.ui.element_format.comment"))
}

row {
AffixDecoratorEditor(
scheme.affixDecorator,
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/randomness.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ affix.ui.option=&Surround with:
array.error.min_count_too_low=Minimum count should be at least %1$s.
array.error.min_count_above_max=Minimum count should be less than or equal to maximum count.
array.title=Array
array.ui.element_format.comment=Write {val} for the element's value. Write {eid} for the index inside the array. Write {aid} for the index of the array when inserting a nested array.
array.ui.element_format.option=Element format:
array.ui.enabled=&Enabled
array.ui.min_count_option=Min c&ount:
array.ui.max_count_option=Max co&unt:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ object ArrayDecoratorEditorTest : FunSpec({
" - ",
)
},
"elementFormat" to {
row(
frame.comboBox("arrayElementFormat").textProp(),
editor.scheme::elementFormat.prop(),
"{val}=foo",
)
},
"affixDecorator" to {
row(
frame.comboBox("arrayAffixDescriptor").textProp(),
Expand Down
65 changes: 52 additions & 13 deletions src/test/kotlin/com/fwdekker/randomness/array/ArrayDecoratorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,48 +26,63 @@ object ArrayDecoratorTest : FunSpec({
"returns default input if disabled" to
row(
ArrayDecorator(enabled = false, minCount = 3),
"{i0}",
"<i0>",
),
"returns a single value" to
row(
ArrayDecorator(enabled = true, minCount = 1, maxCount = 1),
"[{i0}]",
"[<i0>]",
),
"returns a fixed number of values" to
row(
ArrayDecorator(enabled = true, minCount = 3, maxCount = 3),
"[{i0}, {i1}, {i2}]",
"[<i0>, <i1>, <i2>]",
),
"returns array with multi-char separator" to
row(
ArrayDecorator(enabled = true, separator = ";;"),
"[{i0};;{i1};;{i2}]",
"[<i0>;;<i1>;;<i2>]",
),
"retains leading whitespace in separator" to
row(
ArrayDecorator(enabled = true, separator = ", "),
"[{i0}, {i1}, {i2}]",
"[<i0>, <i1>, <i2>]",
),
"converts escaped 'n' in separator to newline" to
row(
ArrayDecorator(enabled = true, separator = """\n"""),
"[{i0}\n{i1}\n{i2}]",
"[<i0>\n<i1>\n<i2>]",
),
"applies the element format to each element separately" to
row(
ArrayDecorator(enabled = true, elementFormat = "({val})"),
"[(<i0>), (<i1>), (<i2>)]",
),
"includes the element index if part of the element format" to
row(
ArrayDecorator(enabled = true, elementFormat = "{eid}={val}"),
"[0=<i0>, 1=<i1>, 2=<i2>]",
),
"ignores unknown brace-delimited keywords in the element format" to
row(
ArrayDecorator(enabled = true, elementFormat = "{val}={foo}"),
"[<i0>={foo}, <i1>={foo}, <i2>={foo}]",
),
"applies affix decorator" to
row(
ArrayDecorator(enabled = true, affixDecorator = AffixDecorator(enabled = true, "(@)")),
"({i0}, {i1}, {i2})",
"(<i0>, <i1>, <i2>)",
),
)
) { (scheme, output) ->
scheme.generator = { count -> List(count) { "{i$it}" } }
scheme.generator = { count -> List(count) { "<i$it>" } }

scheme.generateStrings()[0] shouldBe output
}

test("generates the desired number of parts in each string") {
val scheme = ArrayDecorator(enabled = true, minCount = 3, maxCount = 8)
scheme.generator = { count -> List(count) { "{i$it}" } }
scheme.generator = { count -> List(count) { "<i$it>" } }

scheme.generateStrings(count = 50)
.map { string -> string.count { it == ',' } + 1 }
Expand All @@ -76,7 +91,7 @@ object ArrayDecoratorTest : FunSpec({

test("generates an independently random number of parts per string") {
val scheme = ArrayDecorator(enabled = true, minCount = 1, maxCount = 8)
scheme.generator = { count -> List(count) { "{i$it}" } }
scheme.generator = { count -> List(count) { "<i$it>" } }

scheme.generateStrings(count = 50)
.map { string -> string.count { it == ',' } + 1 }
Expand All @@ -86,9 +101,33 @@ object ArrayDecoratorTest : FunSpec({
test("appropriately splits parts into strings") {
val scheme = ArrayDecorator(enabled = true)
var partIdx = 0
scheme.generator = { count -> List(count) { "{i${partIdx++}}" } }
scheme.generator = { count -> List(count) { "<i${partIdx++}>" } }

scheme.generateStrings(count = 2) shouldBe listOf("[<i0>, <i1>, <i2>]", "[<i3>, <i4>, <i5>]")
}

test("applies the element format to each element of each array") {
val scheme = ArrayDecorator(enabled = true, elementFormat = "({val})")
var partIdx = 0
scheme.generator = { count -> List(count) { "<i${partIdx++}>" } }

scheme.generateStrings(count = 2) shouldBe listOf("[(<i0>), (<i1>), (<i2>)]", "[(<i3>), (<i4>), (<i5>)]")
}

test("starts the element index at 0 for each array") {
val scheme = ArrayDecorator(enabled = true, elementFormat = "{eid}={val}")
var partIdx = 0
scheme.generator = { count -> List(count) { "<i${partIdx++}>" } }

scheme.generateStrings(count = 2) shouldBe listOf("[0=<i0>, 1=<i1>, 2=<i2>]", "[0=<i3>, 1=<i4>, 2=<i5>]")
}

test("includes the array index in each array") {
val scheme = ArrayDecorator(enabled = true, elementFormat = "{aid}={val}")
var partIdx = 0
scheme.generator = { count -> List(count) { "<i${partIdx++}>" } }

scheme.generateStrings(count = 2) shouldBe listOf("[{i0}, {i1}, {i2}]", "[{i3}, {i4}, {i5}]")
scheme.generateStrings(count = 2) shouldBe listOf("[0=<i0>, 0=<i1>, 0=<i2>]", "[1=<i3>, 1=<i4>, 1=<i5>]")
}
}

Expand All @@ -109,7 +148,7 @@ object ArrayDecoratorTest : FunSpec({
row(ArrayDecorator(affixDecorator = AffixDecorator(descriptor = """\""")), ""),
)
) { (scheme, validation) ->
scheme.generator = { count -> List(count) { "{i$it}" } }
scheme.generator = { count -> List(count) { "<i$it>" } }

scheme shouldValidateAsBundle validation
}
Expand Down
Loading