wildcard.ts 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. import { sortBy, pipe } from "remeda"
  2. export namespace Wildcard {
  3. export function match(str: string, pattern: string) {
  4. const regex = new RegExp(
  5. "^" +
  6. pattern
  7. .replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape special regex chars
  8. .replace(/\*/g, ".*") // * becomes .*
  9. .replace(/\?/g, ".") + // ? becomes .
  10. "$",
  11. "s", // s flag enables multiline matching
  12. )
  13. return regex.test(str)
  14. }
  15. export function all(input: string, patterns: Record<string, any>) {
  16. const sorted = pipe(patterns, Object.entries, sortBy([([key]) => key.length, "asc"], [([key]) => key, "asc"]))
  17. let result = undefined
  18. for (const [pattern, value] of sorted) {
  19. if (match(input, pattern)) {
  20. result = value
  21. continue
  22. }
  23. }
  24. return result
  25. }
  26. export function allStructured(input: { head: string; tail: string[] }, patterns: Record<string, any>) {
  27. const sorted = pipe(patterns, Object.entries, sortBy([([key]) => key.length, "asc"], [([key]) => key, "asc"]))
  28. let result = undefined
  29. for (const [pattern, value] of sorted) {
  30. const parts = pattern.split(/\s+/)
  31. if (!match(input.head, parts[0])) continue
  32. if (parts.length === 1 || matchSequence(input.tail, parts.slice(1))) {
  33. result = value
  34. continue
  35. }
  36. }
  37. return result
  38. }
  39. function matchSequence(items: string[], patterns: string[]): boolean {
  40. if (patterns.length === 0) return true
  41. const [pattern, ...rest] = patterns
  42. if (pattern === "*") return matchSequence(items, rest)
  43. for (let i = 0; i < items.length; i++) {
  44. if (match(items[i], pattern) && matchSequence(items.slice(i + 1), rest)) {
  45. return true
  46. }
  47. }
  48. return false
  49. }
  50. }