diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt index 5c1a6bd3..5d9cc7bb 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -1,5 +1,6 @@ package hep.dataforge.names +import hep.dataforge.meta.DFExperimental import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -39,6 +40,18 @@ public class Name(public val tokens: List) { public companion object : KSerializer { public const val NAME_SEPARATOR: String = "." + /** + * Match any single token (both body and index) + */ + @DFExperimental + public val MATCH_ANY_TOKEN: NameToken = NameToken("*") + + /** + * Token that allows to match the whole tail or the whole head of the name. Must match at least one token. + */ + @DFExperimental + public val MATCH_ALL_TOKEN: NameToken = NameToken("**") + public val EMPTY: Name = Name(emptyList()) override val descriptor: SerialDescriptor = diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/nameMatcher.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/nameMatcher.kt new file mode 100644 index 00000000..363109f1 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/nameMatcher.kt @@ -0,0 +1,47 @@ +package hep.dataforge.names + +import hep.dataforge.meta.DFExperimental + + +/** + * Checks if this token matches a given [NameToken]. The match successful if: + * * Token body matches pattern body as a regex + * * Index body matches pattern body as a regex of both are null + */ +@DFExperimental +public fun NameToken.matches(pattern: NameToken): Boolean { + if (pattern == Name.MATCH_ANY_TOKEN) return true + val bodyMatches = body.matches(pattern.body.toRegex()) + val indexMatches = (index == null && pattern.index == null) || pattern.index?.let { patternIndex -> + (index ?: "").matches(patternIndex.toRegex()) + } ?: false + return bodyMatches && indexMatches +} + + +/** + * Matches all names in pattern according to [NameToken.matches] rules. + */ +@DFExperimental +public fun Name.matches(pattern: Name): Boolean = when { + pattern.endsWith(Name.MATCH_ALL_TOKEN) -> { + length >= pattern.length + && Name(tokens.subList(0, pattern.length - 1)).matches(pattern.cutLast()) + } + pattern.startsWith(Name.MATCH_ALL_TOKEN) -> { + length >= pattern.length + && Name(tokens.subList(tokens.size - pattern.length + 1, tokens.size)).matches(pattern.cutFirst()) + } + else -> { + tokens.indices.forEach { + val thisToken = tokens.getOrNull(it) ?: return false + if (thisToken == Name.MATCH_ALL_TOKEN) error("Match-all token in the middle of the name is not supported yet") + val patternToken = pattern.tokens.getOrNull(it) ?: return false + if (!thisToken.matches(patternToken)) return false + } + true + } +} + +@OptIn(DFExperimental::class) +public fun Name.matches(pattern: String): Boolean = matches(pattern.toName()) \ No newline at end of file