From 66d116ed88b06230b010a53e417d73d52a9046b0 Mon Sep 17 00:00:00 2001 From: Davis Date: Fri, 7 Jun 2024 21:35:01 +0200 Subject: [PATCH 1/8] Add GlobToRegex utility for package name matching This commit introduces a new Kotlin file, GlobToRegex.kt, which provides utilities for converting glob patterns to regular expressions, primarily for matching package names in JVM projects. --- .../org/koin/compiler/util/GlobToRegex.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt new file mode 100644 index 0000000..b3509d1 --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt @@ -0,0 +1,37 @@ +package org.koin.compiler.util + +object GlobToRegex { + private const val DOT = '.' + private const val ESCAPED_DOT = "\\$DOT" + private const val NOT_DOT = "[^$DOT]+" + + private const val MULTI_LEVEL_WILDCARD = "**" + private const val SINGLE_LEVEL_WILDCARD = "*" + + private const val SINGLE_LEVEL_PATTERN = "$NOT_DOT$ESCAPED_DOT?" + private const val MULTI_LEVEL_PATTERN = "($SINGLE_LEVEL_PATTERN)*" + + fun convert(globPattern: String, ignoreCase: Boolean = false): Regex { + val parts = globPattern.split(DOT) + val regexParts = parts.mapIndexed { index, part -> + when { + part == MULTI_LEVEL_WILDCARD -> multiLevelPatternFor(index == parts.lastIndex) + part.contains(SINGLE_LEVEL_WILDCARD) -> part.replace(SINGLE_LEVEL_WILDCARD, SINGLE_LEVEL_PATTERN) + else -> Regex.escape(part) + if (index < parts.lastIndex) ESCAPED_DOT else "" + } + } + return with("^${regexParts.joinToString("")}$") { + if (ignoreCase) toRegex(RegexOption.IGNORE_CASE) else toRegex() + } + } + + private fun multiLevelPatternFor(isLast: Boolean) = + if (isLast) "$MULTI_LEVEL_PATTERN$NOT_DOT" else MULTI_LEVEL_PATTERN +} + +fun String.toGlobRegex(ignoreCase: Boolean = false): Regex = GlobToRegex.convert(this, ignoreCase) + +fun Map.containsGlob(keyGlob: String): Boolean = keys.any { keyGlob.toGlobRegex().matches(it) } + +fun String.containsGlob(glob: String, ignoreCase: Boolean = false): Boolean = + glob.toGlobRegex(ignoreCase = ignoreCase).matches(this) \ No newline at end of file From 6812089cff0d953d5496c7a951ffbba0c2283326 Mon Sep 17 00:00:00 2001 From: Davis Date: Fri, 7 Jun 2024 21:38:39 +0200 Subject: [PATCH 2/8] Apply package glob pattern matching --- .../jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt | 3 ++- .../kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt index 7b3ffe4..1cbc7f7 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt @@ -18,6 +18,7 @@ package org.koin.compiler.metadata import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.Visibility +import org.koin.compiler.util.containsGlob import java.util.* sealed class KoinMetaData { @@ -44,7 +45,7 @@ sealed class KoinMetaData { fun acceptDefinition(defPackageName: String): Boolean { return when { componentScan == null -> false - componentScan.packageName.isNotEmpty() -> defPackageName.contains( + componentScan.packageName.isNotEmpty() -> defPackageName.containsGlob( componentScan.packageName, ignoreCase = true ) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt index 4a79642..a92195a 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt @@ -23,6 +23,7 @@ import com.google.devtools.ksp.symbol.KSFunctionDeclaration import com.google.devtools.ksp.validate import org.koin.compiler.metadata.DEFINITION_ANNOTATION_LIST_TYPES import org.koin.compiler.metadata.KoinMetaData +import org.koin.compiler.util.containsGlob import org.koin.core.annotation.Module class KoinMetaDataScanner( @@ -81,7 +82,7 @@ class KoinMetaDataScanner( module.componentScan?.let { scan -> when (scan.packageName) { "" -> emptyScanList.add(module) - else -> if (moduleList.contains(scan.packageName)) { + else -> if (moduleList.containsGlob(scan.packageName)) { val existing = moduleList[scan.packageName]!! error("@ComponentScan with '${scan.packageName}' from module ${module.name} is already declared in ${existing.name}. Please fix @ComponentScan value ") } else { From 05bfa218a0a4c2baed4254f25731a93dcffee68b Mon Sep 17 00:00:00 2001 From: Davis Date: Fri, 7 Jun 2024 21:39:45 +0200 Subject: [PATCH 3/8] Fix whitespace in packageNamePrefix declaration --- .../jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt index 1cbc7f7..a176691 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt @@ -87,7 +87,7 @@ sealed class KoinMetaData { fun isNotScoped(): Boolean = !isScoped() fun isType(keyword: DefinitionAnnotation): Boolean = this.keyword == keyword - val packageNamePrefix : String = if (packageName.isEmpty()) "" else "${packageName}." + val packageNamePrefix: String = if (packageName.isEmpty()) "" else "${packageName}." override fun equals(other: Any?): Boolean { if (this === other) return true From bd702feaeb19c2ef8ba6df8cc0ddcc0f45be1ec9 Mon Sep 17 00:00:00 2001 From: Davis Date: Fri, 7 Jun 2024 22:54:48 +0200 Subject: [PATCH 4/8] Add support for wildcard pattern Suffix and prefix wildcard pattern like are now supported --- .../kotlin/org/koin/compiler/util/GlobToRegex.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt index b3509d1..2605e67 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt @@ -3,7 +3,7 @@ package org.koin.compiler.util object GlobToRegex { private const val DOT = '.' private const val ESCAPED_DOT = "\\$DOT" - private const val NOT_DOT = "[^$DOT]+" + private const val NOT_DOT = "[^$DOT]*" private const val MULTI_LEVEL_WILDCARD = "**" private const val SINGLE_LEVEL_WILDCARD = "*" @@ -14,10 +14,13 @@ object GlobToRegex { fun convert(globPattern: String, ignoreCase: Boolean = false): Regex { val parts = globPattern.split(DOT) val regexParts = parts.mapIndexed { index, part -> - when { - part == MULTI_LEVEL_WILDCARD -> multiLevelPatternFor(index == parts.lastIndex) - part.contains(SINGLE_LEVEL_WILDCARD) -> part.replace(SINGLE_LEVEL_WILDCARD, SINGLE_LEVEL_PATTERN) - else -> Regex.escape(part) + if (index < parts.lastIndex) ESCAPED_DOT else "" + when (part) { + MULTI_LEVEL_WILDCARD -> multiLevelPatternFor(index == parts.lastIndex) + SINGLE_LEVEL_WILDCARD -> SINGLE_LEVEL_PATTERN + else -> part.replace( + SINGLE_LEVEL_WILDCARD, + NOT_DOT + ) + if (index < parts.lastIndex) ESCAPED_DOT else "" } } return with("^${regexParts.joinToString("")}$") { From 649d81c69a3eaa4bad2b9a294eba7895b39c1aea Mon Sep 17 00:00:00 2001 From: Davis Date: Fri, 7 Jun 2024 23:13:54 +0200 Subject: [PATCH 5/8] Fix SINGLE_LEVEL_PATTERN Now `com.example.*.service` does ot match `com.example.service`. --- .../src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt index 2605e67..04dc034 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt @@ -8,7 +8,7 @@ object GlobToRegex { private const val MULTI_LEVEL_WILDCARD = "**" private const val SINGLE_LEVEL_WILDCARD = "*" - private const val SINGLE_LEVEL_PATTERN = "$NOT_DOT$ESCAPED_DOT?" + private const val SINGLE_LEVEL_PATTERN = "$NOT_DOT$ESCAPED_DOT" private const val MULTI_LEVEL_PATTERN = "($SINGLE_LEVEL_PATTERN)*" fun convert(globPattern: String, ignoreCase: Boolean = false): Regex { From 3deea3bd2dfbc71fcb5276d65919fb382d347d5b Mon Sep 17 00:00:00 2001 From: Davis Date: Fri, 7 Jun 2024 23:14:29 +0200 Subject: [PATCH 6/8] Add documentation --- .../koin/core/annotation/CoreAnnotations.kt | 21 +++++++- .../org/koin/compiler/util/GlobToRegex.kt | 54 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/projects/koin-annotations/src/commonMain/kotlin/org/koin/core/annotation/CoreAnnotations.kt b/projects/koin-annotations/src/commonMain/kotlin/org/koin/core/annotation/CoreAnnotations.kt index 004e18c..06b052b 100644 --- a/projects/koin-annotations/src/commonMain/kotlin/org/koin/core/annotation/CoreAnnotations.kt +++ b/projects/koin-annotations/src/commonMain/kotlin/org/koin/core/annotation/CoreAnnotations.kt @@ -180,7 +180,26 @@ annotation class Module(val includes: Array> = [], val createdAtStart: * Gather definitions declared with Koin definition annotation * Will scan in current package or with the explicit package name * - * @param value: package to scan + * The [value] parameter supports both exact package names and glob patterns: + * + * 1. Exact package: `"com.example.service"` + * - Scans only the `com.example.service` package. + * + * 2. Single-level wildcard (`*`): `"com.example.*.service"` + * - Matches one level of package hierarchy. + * - E.g., `com.example.user.service`, `com.example.order.service`. + * - Does NOT match `com.example.service` or `com.example.user.impl.service`. + * + * 3. Multi-level wildcard (`**`): `"com.example.**"` + * - Matches any number of package levels. + * - E.g., `com.example`, `com.example.service`, `com.example.service.user`. + * + * Wildcards can be combined and used at any level: + * - `"com.**.service.*data"`: All packages that ends with "data" in any `service` subpackage. + * - `"com.*.service.**"`: All classes in `com.X.service` and its subpackages. + * + * @param value The package to scan. Can be an exact package name or a glob pattern. + * Defaults to the package of the annotated element if empty. */ @Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD) annotation class ComponentScan(val value: String = "") \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt index 04dc034..f6f9566 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt @@ -1,5 +1,22 @@ package org.koin.compiler.util +/** + * A utility object for converting glob patterns to regular expressions, primarily used for matching package names. + * + * Glob patterns are a simple way to match file paths and, in this context, package names. + * They use wildcards to represent variable parts of the name: + * + * - `*`: Matches any sequence of characters within a single package level. + * Example: `com.example.*` matches `com.example.foo` but not `com.example.foo.bar`. + * + * - `**`: Matches any sequence of characters across multiple package levels. + * Example: `com.example.**` matches both `com.example.foo` and `com.example.foo.bar`. + * + * @author OffRange + * @see [convert] + * @see [String.toGlobRegex] + * @see [String.containsGlob] + */ object GlobToRegex { private const val DOT = '.' private const val ESCAPED_DOT = "\\$DOT" @@ -11,6 +28,20 @@ object GlobToRegex { private const val SINGLE_LEVEL_PATTERN = "$NOT_DOT$ESCAPED_DOT" private const val MULTI_LEVEL_PATTERN = "($SINGLE_LEVEL_PATTERN)*" + /** + * Converts a glob pattern to a regular expression. + * + * Supports two types of wildcards: + * - `*`: Matches any characters within a single package level. + * - `**`: Matches any characters across multiple package levels. + * + * @param globPattern the glob pattern to convert, e.g., "com.example.**.service.*" + * @param ignoreCase if true, the resulting regex will be case-insensitive. Default is false, + * as package names in most JVM languages are case-sensitive. + * @return a [Regex] object that matches strings according to the given glob pattern. + * + * @throws IllegalArgumentException if the glob pattern is invalid or cannot be converted. + */ fun convert(globPattern: String, ignoreCase: Boolean = false): Regex { val parts = globPattern.split(DOT) val regexParts = parts.mapIndexed { index, part -> @@ -32,9 +63,30 @@ object GlobToRegex { if (isLast) "$MULTI_LEVEL_PATTERN$NOT_DOT" else MULTI_LEVEL_PATTERN } +/** + * Converts this string, interpreted as a glob pattern, to a regular expression. + * + * @param ignoreCase if true, the resulting regex will be case-insensitive. + * Default is false, as package names are typically case-sensitive. + * @return a [Regex] object that matches strings according to this glob pattern. + */ fun String.toGlobRegex(ignoreCase: Boolean = false): Regex = GlobToRegex.convert(this, ignoreCase) -fun Map.containsGlob(keyGlob: String): Boolean = keys.any { keyGlob.toGlobRegex().matches(it) } +/** + * Checks if this map contains a key that matches the given glob pattern. + * + * @param keyGlob the glob pattern to match against keys. + * @return true if any key matches the glob pattern, false otherwise. + */ +operator fun Map.contains(keyGlob: String): Boolean = keys.any { keyGlob.toGlobRegex().matches(it) } +/** + * Checks if this string matches the given glob pattern. + * + * @param glob the glob pattern to match. + * @param ignoreCase if true, the match will be case-insensitive. + * Default is false, as package and class names are typically case-sensitive. + * @return true if this string matches the glob pattern, false otherwise. + */ fun String.containsGlob(glob: String, ignoreCase: Boolean = false): Boolean = glob.toGlobRegex(ignoreCase = ignoreCase).matches(this) \ No newline at end of file From 87822ff9843f61becc04abee17da16df51c94749 Mon Sep 17 00:00:00 2001 From: Davis Date: Wed, 26 Jun 2024 13:42:34 +0200 Subject: [PATCH 7/8] Refactor glob function names --- .../kotlin/org/koin/compiler/metadata/KoinMetaData.kt | 4 ++-- .../kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt | 4 ++-- .../jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt index a176691..bc47126 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt @@ -18,7 +18,7 @@ package org.koin.compiler.metadata import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.Visibility -import org.koin.compiler.util.containsGlob +import org.koin.compiler.util.matchesGlob import java.util.* sealed class KoinMetaData { @@ -45,7 +45,7 @@ sealed class KoinMetaData { fun acceptDefinition(defPackageName: String): Boolean { return when { componentScan == null -> false - componentScan.packageName.isNotEmpty() -> defPackageName.containsGlob( + componentScan.packageName.isNotEmpty() -> defPackageName.matchesGlob( componentScan.packageName, ignoreCase = true ) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt index a92195a..8f5dad6 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt @@ -23,7 +23,7 @@ import com.google.devtools.ksp.symbol.KSFunctionDeclaration import com.google.devtools.ksp.validate import org.koin.compiler.metadata.DEFINITION_ANNOTATION_LIST_TYPES import org.koin.compiler.metadata.KoinMetaData -import org.koin.compiler.util.containsGlob +import org.koin.compiler.util.anyMatch import org.koin.core.annotation.Module class KoinMetaDataScanner( @@ -82,7 +82,7 @@ class KoinMetaDataScanner( module.componentScan?.let { scan -> when (scan.packageName) { "" -> emptyScanList.add(module) - else -> if (moduleList.containsGlob(scan.packageName)) { + else -> if (moduleList.anyMatch(scan.packageName)) { val existing = moduleList[scan.packageName]!! error("@ComponentScan with '${scan.packageName}' from module ${module.name} is already declared in ${existing.name}. Please fix @ComponentScan value ") } else { diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt index f6f9566..fd6385c 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt @@ -15,7 +15,7 @@ package org.koin.compiler.util * @author OffRange * @see [convert] * @see [String.toGlobRegex] - * @see [String.containsGlob] + * @see [String.matchesGlob] */ object GlobToRegex { private const val DOT = '.' @@ -78,7 +78,7 @@ fun String.toGlobRegex(ignoreCase: Boolean = false): Regex = GlobToRegex.convert * @param keyGlob the glob pattern to match against keys. * @return true if any key matches the glob pattern, false otherwise. */ -operator fun Map.contains(keyGlob: String): Boolean = keys.any { keyGlob.toGlobRegex().matches(it) } +fun Map.anyMatch(keyGlob: String): Boolean = keys.any { keyGlob.toGlobRegex().matches(it) } /** * Checks if this string matches the given glob pattern. @@ -88,5 +88,5 @@ operator fun Map.contains(keyGlob: String): Boolean = keys.any { keyG * Default is false, as package and class names are typically case-sensitive. * @return true if this string matches the glob pattern, false otherwise. */ -fun String.containsGlob(glob: String, ignoreCase: Boolean = false): Boolean = +fun String.matchesGlob(glob: String, ignoreCase: Boolean = false): Boolean = glob.toGlobRegex(ignoreCase = ignoreCase).matches(this) \ No newline at end of file From 1e423632f24a984bbc998b84eaf517055a94a1fc Mon Sep 17 00:00:00 2001 From: Davis Date: Wed, 26 Jun 2024 15:40:27 +0200 Subject: [PATCH 8/8] Fix glob pattern --- .../org/koin/compiler/util/GlobToRegex.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt index fd6385c..bbe9f13 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/util/GlobToRegex.kt @@ -20,13 +20,13 @@ package org.koin.compiler.util object GlobToRegex { private const val DOT = '.' private const val ESCAPED_DOT = "\\$DOT" - private const val NOT_DOT = "[^$DOT]*" + private const val NOT_DOT = "[^$DOT]" private const val MULTI_LEVEL_WILDCARD = "**" private const val SINGLE_LEVEL_WILDCARD = "*" - private const val SINGLE_LEVEL_PATTERN = "$NOT_DOT$ESCAPED_DOT" - private const val MULTI_LEVEL_PATTERN = "($SINGLE_LEVEL_PATTERN)*" + private const val GENERAL_SINGLE_LEVEL_PATTERN = "$NOT_DOT*" + private const val GENERAL_MULTI_LEVEL_PATTERN = "($GENERAL_SINGLE_LEVEL_PATTERN$ESCAPED_DOT)*$NOT_DOT+" /** * Converts a glob pattern to a regular expression. @@ -44,23 +44,20 @@ object GlobToRegex { */ fun convert(globPattern: String, ignoreCase: Boolean = false): Regex { val parts = globPattern.split(DOT) - val regexParts = parts.mapIndexed { index, part -> + val regexParts = parts.map { part -> when (part) { - MULTI_LEVEL_WILDCARD -> multiLevelPatternFor(index == parts.lastIndex) - SINGLE_LEVEL_WILDCARD -> SINGLE_LEVEL_PATTERN + MULTI_LEVEL_WILDCARD -> GENERAL_MULTI_LEVEL_PATTERN + SINGLE_LEVEL_WILDCARD -> GENERAL_SINGLE_LEVEL_PATTERN else -> part.replace( SINGLE_LEVEL_WILDCARD, - NOT_DOT - ) + if (index < parts.lastIndex) ESCAPED_DOT else "" + GENERAL_SINGLE_LEVEL_PATTERN + ) } } - return with("^${regexParts.joinToString("")}$") { + return with("^${regexParts.joinToString(ESCAPED_DOT)}$") { if (ignoreCase) toRegex(RegexOption.IGNORE_CASE) else toRegex() } } - - private fun multiLevelPatternFor(isLast: Boolean) = - if (isLast) "$MULTI_LEVEL_PATTERN$NOT_DOT" else MULTI_LEVEL_PATTERN } /**