Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* Minimum IDE version has been increased to 2024.3. This helps avert compatibility issues with future versions. ([#TODO](https://github.com/fwdekker/intellij-randomness/issues/TODO))

### Added
* Added support for Nano ID. ([#TODO](https://github.com/fwdekker/intellij-randomness/issues/TODO))

### Changed
* Regex patterns now ignore named capturing groups instead of giving an error. Achieved by updating [RgxGen](https://github.com/curious-odd-man/RgxGen) to v3.1. ([#TODO](https://github.com/fwdekker/intellij-randomness/issues/TODO))

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Randomness can also be found in the main menu under <kbd>Tools</kbd> and under <
3. **Strings**, such as `"PaQDQqSBEH"`, with support for reverse regex.
4. **Words**, such as `"Bridge"`, with predefined or custom word lists.
5. **UUIDs**, such as `0caa7b28-fe58-4ba6-a25a-9e5beaaf8f4b`, with or without dashes.
6. **Date-times**, such as `2022-02-03 19:03`, or any other format you want.
6. **Nano IDs**, such as `V1StGXR8_Z5jdHi6B-myT`, with customisable alphabets and sizes.
7. **Date-times**, such as `2022-02-03 19:03`, or any other format you want.
* 🧬 **Templates**<br />
For complex kinds of data, you can use templates.
A template is a list of data types that should be concatenated to create random data.
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies {
}
implementation("com.github.curious-odd-man", "rgxgen", properties("rgxgenVersion"))
implementation("org.eclipse.mylyn.github", "org.eclipse.egit.github.core", properties("githubCore"))
implementation("io.viascom.nanoid", "nanoid", properties("nanoidVersion"))
scrambler("org.jetbrains.kotlin:kotlin-reflect")

testImplementation("org.assertj", "assertj-swing-junit", properties("assertjSwingVersion"))
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ githubCore=2.1.5
kotestVersion=5.9.1
rgxgenVersion=3.1
uuidGeneratorVersion=5.1.0
nanoidVersion=1.0.1

# Gradle
org.gradle.caching=true
Expand Down
77 changes: 77 additions & 0 deletions src/main/kotlin/com/fwdekker/randomness/nanoid/NanoIdScheme.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.fwdekker.randomness.nanoid

import com.fwdekker.randomness.Bundle
import com.fwdekker.randomness.Icons
import com.fwdekker.randomness.Scheme
import com.fwdekker.randomness.TypeIcon
import com.fwdekker.randomness.affix.AffixDecorator
import com.fwdekker.randomness.array.ArrayDecorator
import com.fwdekker.randomness.ui.ValidatorDsl.Companion.validators
import com.intellij.ui.JBColor
import com.intellij.util.xmlb.annotations.OptionTag
import io.viascom.nanoid.NanoId
import java.awt.Color

/**
* Contains settings for generating Nano IDs.
*
* @property size The length of the generated Nano ID.
* @property alphabet The alphabet to use when generating the Nano ID.
* @property affixDecorator The affixation to apply to the generated values.
* @property arrayDecorator Settings that determine whether the output should be an array of values.
*/
data class NanoIdScheme(
var size: Int = DEFAULT_SIZE,
var alphabet: String = DEFAULT_ALPHABET,
@OptionTag val affixDecorator: AffixDecorator = DEFAULT_AFFIX_DECORATOR,
@OptionTag val arrayDecorator: ArrayDecorator = DEFAULT_ARRAY_DECORATOR,
) : Scheme() {
override val name = Bundle("nanoid.title")
override val typeIcon get() = BASE_ICON
override val decorators get() = listOf(affixDecorator, arrayDecorator)
override val validators = validators {
of(::size)
.check({ it >= MIN_SIZE }, { Bundle("nanoid.error.size_too_low", MIN_SIZE) })
of(::alphabet)
.check({ it.isNotEmpty() }, { Bundle("nanoid.error.alphabet_empty") })
include(::affixDecorator)
include(::arrayDecorator)
}

override fun generateUndecoratedStrings(count: Int): List<String> =
List(count) { NanoId.generate(size, alphabet) }

override fun deepCopy(retainUuid: Boolean) =
copy(
affixDecorator = affixDecorator.deepCopy(retainUuid),
arrayDecorator = arrayDecorator.deepCopy(retainUuid),
).deepCopyTransient(retainUuid)

/**
* Holds constants.
*/
companion object {
/** The base icon for Nano IDs. */
val BASE_ICON
get() = TypeIcon(Icons.SCHEME, "id", listOf(JBColor(Color(185, 155, 248, 154), Color(185, 155, 248, 154))))


/** The minimum allowed value of [size]. */
const val MIN_SIZE = 1

/** The default value of [size]. */
const val DEFAULT_SIZE = 21

/** The default value of [alphabet]. */
const val DEFAULT_ALPHABET: String = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

/** The preset values for the [affixDecorator] field. */
val PRESET_AFFIX_DECORATOR_DESCRIPTORS = listOf("'", "\"", "`")

/** The default value of the [affixDecorator] field. */
val DEFAULT_AFFIX_DECORATOR get() = AffixDecorator(enabled = false, descriptor = "\"")

/** The default value of the [arrayDecorator] field. */
val DEFAULT_ARRAY_DECORATOR get() = ArrayDecorator()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.fwdekker.randomness.nanoid

import com.fwdekker.randomness.Bundle
import com.fwdekker.randomness.SchemeEditor
import com.fwdekker.randomness.affix.AffixDecoratorEditor
import com.fwdekker.randomness.array.ArrayDecoratorEditor
import com.fwdekker.randomness.nanoid.NanoIdScheme.Companion.DEFAULT_SIZE
import com.fwdekker.randomness.nanoid.NanoIdScheme.Companion.PRESET_AFFIX_DECORATOR_DESCRIPTORS
import com.fwdekker.randomness.ui.JIntSpinner
import com.fwdekker.randomness.ui.UIConstants
import com.fwdekker.randomness.ui.bindIntValue
import com.fwdekker.randomness.ui.withFixedWidth
import com.fwdekker.randomness.ui.withName
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.bindText
import com.intellij.ui.dsl.builder.panel

/**
* Component for editing a [NanoIdScheme].
*
* @param scheme the scheme to edit
*/
class NanoIdSchemeEditor(scheme: NanoIdScheme = NanoIdScheme()) : SchemeEditor<NanoIdScheme>(scheme) {
override val rootComponent = panel {
group(Bundle("nanoid.ui.value.header")) {
row(Bundle("nanoid.ui.value.size_option")) {
cell(JIntSpinner(DEFAULT_SIZE, NanoIdScheme.MIN_SIZE, Int.MAX_VALUE))
.withFixedWidth(UIConstants.SIZE_SMALL)
.withName("size")
.bindIntValue(scheme::size)
.bindValidation(scheme::size)
}
row(Bundle("nanoid.ui.value.alphabet_option")) {
textField()
.withName("alphabet")
.bindText(scheme::alphabet)
.bindValidation(scheme::alphabet)
}
row {
AffixDecoratorEditor(scheme.affixDecorator, PRESET_AFFIX_DECORATOR_DESCRIPTORS)
.also { decoratorEditors += it }
.let { cell(it.rootComponent) }
}
}
row {
ArrayDecoratorEditor(scheme.arrayDecorator)
.also { decoratorEditors += it }
.let { cell(it.rootComponent).align(AlignX.FILL) }
}
}.finalize(this)

init {
reset()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.fwdekker.randomness.Scheme
import com.fwdekker.randomness.datetime.DateTimeScheme
import com.fwdekker.randomness.decimal.DecimalScheme
import com.fwdekker.randomness.integer.IntegerScheme
import com.fwdekker.randomness.nanoid.NanoIdScheme
import com.fwdekker.randomness.string.StringScheme
import com.fwdekker.randomness.uuid.UuidScheme
import com.fwdekker.randomness.word.WordScheme
Expand Down Expand Up @@ -711,6 +712,7 @@ class TemplateJTree(
StringScheme(),
WordScheme(),
UuidScheme(),
NanoIdScheme(),
DateTimeScheme(),
TemplateReference(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.fwdekker.randomness.array.ArrayDecorator
import com.fwdekker.randomness.datetime.DateTimeScheme
import com.fwdekker.randomness.decimal.DecimalScheme
import com.fwdekker.randomness.integer.IntegerScheme
import com.fwdekker.randomness.nanoid.NanoIdScheme
import com.fwdekker.randomness.string.StringScheme
import com.fwdekker.randomness.ui.ValidatorDsl.Companion.validators
import com.fwdekker.randomness.uuid.UuidScheme
Expand Down Expand Up @@ -134,6 +135,7 @@ data class TemplateList(
)
),
Template("UUID", mutableListOf(UuidScheme())),
Template("Nano ID", mutableListOf(NanoIdScheme())),
Template("Date-Time", mutableListOf(DateTimeScheme())),
Template(
"IP address",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.fwdekker.randomness.decimal.DecimalScheme
import com.fwdekker.randomness.decimal.DecimalSchemeEditor
import com.fwdekker.randomness.integer.IntegerScheme
import com.fwdekker.randomness.integer.IntegerSchemeEditor
import com.fwdekker.randomness.nanoid.NanoIdScheme
import com.fwdekker.randomness.nanoid.NanoIdSchemeEditor
import com.fwdekker.randomness.setAll
import com.fwdekker.randomness.string.StringScheme
import com.fwdekker.randomness.string.StringSchemeEditor
Expand Down Expand Up @@ -154,6 +156,7 @@ class TemplateListEditor(
is DecimalScheme -> DecimalSchemeEditor(scheme)
is StringScheme -> StringSchemeEditor(scheme)
is UuidScheme -> UuidSchemeEditor(scheme)
is NanoIdScheme -> NanoIdSchemeEditor(scheme)
is WordScheme -> WordSchemeEditor(scheme)
is DateTimeScheme -> DateTimeSchemeEditor(scheme)
is TemplateReference -> TemplateReferenceEditor(scheme)
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/randomness.properties
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ uuid.ui.value.version.6=Time, reordered
uuid.ui.value.version.7=Time, epoch
uuid.ui.value.version.8=Random
uuid.ui.value.version.option=Version:
nanoid.title=Nano ID
nanoid.ui.value.header=Value
nanoid.ui.value.size_option=Le&ngth:
nanoid.ui.value.alphabet_option=&Alphabet:
nanoid.error.size_too_low=Length should be at least %1$s.
nanoid.error.alphabet_empty=Alphabet must not be empty.
word.error.empty_word_list=Enter at least one word.
word.title=Word
word.ui.format.capitalization_option=&Capitalization:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.fwdekker.randomness.nanoid

import com.fwdekker.randomness.testhelpers.Tags
import com.fwdekker.randomness.testhelpers.afterNonContainer
import com.fwdekker.randomness.testhelpers.beforeNonContainer
import com.fwdekker.randomness.testhelpers.editorApplyTests
import com.fwdekker.randomness.testhelpers.editorFieldsTests
import com.fwdekker.randomness.testhelpers.prop
import com.fwdekker.randomness.testhelpers.runEdt
import com.fwdekker.randomness.testhelpers.textProp
import com.fwdekker.randomness.testhelpers.useBareIdeaFixture
import com.fwdekker.randomness.testhelpers.useEdtViolationDetection
import com.fwdekker.randomness.testhelpers.valueProp
import io.kotest.core.spec.style.FunSpec
import io.kotest.data.row
import org.assertj.swing.fixture.Containers.showInFrame
import org.assertj.swing.fixture.FrameFixture

/**
* Unit tests for [NanoIdSchemeEditor].
*/
object NanoIdSchemeEditorTest : FunSpec({
tags(Tags.EDITOR)

lateinit var frame: FrameFixture

lateinit var scheme: NanoIdScheme
lateinit var editor: NanoIdSchemeEditor

useEdtViolationDetection()
useBareIdeaFixture()

beforeNonContainer {
scheme = NanoIdScheme()
editor = runEdt { NanoIdSchemeEditor(scheme) }
frame = showInFrame(editor.rootComponent)
}

afterNonContainer {
frame.cleanUp()
}

include(editorApplyTests { editor })

include(
editorFieldsTests(
{ editor },
mapOf(
"size" to {
row(
frame.spinner("size").valueProp(),
editor.scheme::size.prop(),
37,
)
},
"alphabet" to {
row(
frame.textBox("alphabet").textProp(),
editor.scheme::alphabet.prop(),
"abc123",
)
},
"affixDecorator" to {
row(
frame.comboBox("affixDescriptor").textProp(),
editor.scheme.affixDecorator::descriptor.prop(),
"[@]",
)
},
"arrayDecorator" to {
row(
frame.spinner("arrayMaxCount").valueProp(),
editor.scheme.arrayDecorator::maxCount.prop(),
7,
)
},
)
)
)
})
77 changes: 77 additions & 0 deletions src/test/kotlin/com/fwdekker/randomness/nanoid/NanoIdSchemeTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.fwdekker.randomness.nanoid

import com.fwdekker.randomness.affix.AffixDecorator
import com.fwdekker.randomness.array.ArrayDecorator
import com.fwdekker.randomness.testhelpers.Tags
import com.fwdekker.randomness.testhelpers.shouldValidateAsBundle
import com.fwdekker.randomness.testhelpers.stateDeepCopyTestFactory
import com.fwdekker.randomness.testhelpers.stateSerializationTestFactory
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeUnique
import io.kotest.matchers.shouldBe

/**
* Unit tests for [NanoIdScheme].
*/
object NanoIdSchemeTest : FunSpec({
tags(Tags.PLAIN, Tags.SCHEME)

context("generateStrings") {
test("generates IDs of configured length") {
val scheme = NanoIdScheme(size = 13)
val id = scheme.generateStrings()[0]
id.length shouldBe 13
}

test("generated characters are from configured alphabet") {
val scheme = NanoIdScheme(size = 100, alphabet = "abc")
val id = scheme.generateStrings()[0]
id.all { it in scheme.alphabet } shouldBe true
}

test("generates the requested count") {
val scheme = NanoIdScheme(size = 8)
val values = scheme.generateStrings(10)
values.size shouldBe 10
// Most likely unique; not strictly required but a good smoke test
values.shouldBeUnique()
}

test("applies decorators in order affix, array") {
val scheme = NanoIdScheme(
size = 5,
affixDecorator = AffixDecorator(enabled = true, descriptor = "#@"),
arrayDecorator = ArrayDecorator(enabled = true, minCount = 3, maxCount = 3),
)
val out = scheme.generateStrings()[0]
// Expect the affix applied to each element produced by the array decorator -> 3 prefixes
out.count { it == '#' } shouldBe 3
}
}

context("doValidate") {
test("succeeds for default state") {
NanoIdScheme() shouldValidateAsBundle null
}

test("fails for too small size") {
NanoIdScheme(size = 0) shouldValidateAsBundle "nanoid.error.size_too_low"
}

test("fails for empty alphabet") {
NanoIdScheme(alphabet = "") shouldValidateAsBundle "nanoid.error.alphabet_empty"
}

test("fails if affix decorator is invalid") {
NanoIdScheme(affixDecorator = AffixDecorator(enabled = true, descriptor = "\\")) shouldValidateAsBundle ""
}

test("fails if array decorator is invalid") {
NanoIdScheme(arrayDecorator = ArrayDecorator(enabled = true, minCount = -1)) shouldValidateAsBundle ""
}
}

include(stateDeepCopyTestFactory { NanoIdScheme() })

include(stateSerializationTestFactory { NanoIdScheme() })
})