summary refs log tree commit diff stats
path: root/tests/deps/jester-#head/jester/patterns.nim
blob: c827fbc7f6e3d187958e84b6485f8741bf8d58f7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# Copyright (C) 2012-2018 Dominik Picheta
# MIT License - Look at license.txt for details.
import parseutils, tables
type
  NodeType* = enum
    NodeText, NodeField
  Node* = object
    typ*: NodeType
    text*: string
    optional*: bool
  
  Pattern* = seq[Node]

#/show/@id/?
proc parsePattern*(pattern: string): Pattern =
  result = @[]
  template addNode(result: var Pattern, theT: NodeType, theText: string,
                   isOptional: bool): typed =
    block:
      var newNode: Node
      newNode.typ = theT
      newNode.text = theText
      newNode.optional = isOptional
      result.add(newNode)

  template `{}`(s: string, i: int): char =
    if i >= len(s):
      '\0'
    else:
      s[i]

  var i = 0
  var text = ""
  while i < pattern.len():
    case pattern[i]
    of '@':
      # Add the stored text.
      if text != "":
        result.addNode(NodeText, text, false)
        text = ""
      # Parse named parameter.
      inc(i) # Skip @
      var nparam = ""
      i += pattern.parseUntil(nparam, {'/', '?'}, i)
      var optional = pattern{i} == '?'
      result.addNode(NodeField, nparam, optional)
      if pattern{i} == '?': inc(i) # Only skip ?. / should not be skipped.
    of '?':
      var optionalChar = text[^1]
      setLen(text, text.len-1) # Truncate ``text``.
      # Add the stored text.
      if text != "":
        result.addNode(NodeText, text, false)
        text = ""
      # Add optional char.
      inc(i) # Skip ?
      result.addNode(NodeText, $optionalChar, true)
    of '\\':
      inc i # Skip \
      if pattern[i] notin {'?', '@', '\\'}:
        raise newException(ValueError, 
                "This character does not require escaping: " & pattern[i])
      text.add(pattern{i})
      inc i # Skip ``pattern[i]``
    else:
      text.add(pattern{i})
      inc(i)
  
  if text != "":
    result.addNode(NodeText, text, false)

proc findNextText(pattern: Pattern, i: int, toNode: var Node): bool =
  ## Finds the next NodeText in the pattern, starts looking from ``i``.
  result = false
  for n in i..pattern.len()-1:
    if pattern[n].typ == NodeText:
      toNode = pattern[n]
      return true

proc check(n: Node, s: string, i: int): bool =
  let cutTo = (n.text.len-1)+i
  if cutTo > s.len-1: return false
  return s.substr(i, cutTo) == n.text

proc match*(pattern: Pattern, s: string):
      tuple[matched: bool, params: Table[string, string]] =
  var i = 0 # Location in ``s``.

  result.matched = true
  result.params = initTable[string, string]()

  for ncount, node in pattern:
    case node.typ
    of NodeText:
      if node.optional:
        if check(node, s, i):
          inc(i, node.text.len) # Skip over this optional character.
        else:
          # If it's not there, we have nothing to do. It's optional after all.
          discard
      else:
        if check(node, s, i):
          inc(i, node.text.len) # Skip over this
        else:
          # No match.
          result.matched = false
          return
    of NodeField:
      var nextTxtNode: Node
      var stopChar = '/'
      if findNextText(pattern, ncount, nextTxtNode):
        stopChar = nextTxtNode.text[0]
      var matchNamed = ""
      i += s.parseUntil(matchNamed, stopChar, i)
      result.params[node.text] = matchNamed
      if matchNamed == "" and not node.optional:
        result.matched = false
        return

  if s.len != i:
    result.matched = false

when true:
  let f = parsePattern("/show/@id/test/@show?/?")
  doAssert match(f, "/show/12/test/hallo/").matched
  doAssert match(f, "/show/2131726/test/jjjuuwąąss").matched
  doAssert(not match(f, "/").matched)
  doAssert(not match(f, "/show//test//").matched)
  doAssert(match(f, "/show/asd/test//").matched)
  doAssert(not match(f, "/show/asd/asd/test/jjj/").matched)
  doAssert(match(f, "/show/@łę¶ŧ←/test/asd/").params["id"] == "@łę¶ŧ←")

  let f2 = parsePattern("/test42/somefile.?@ext?/?")
  doAssert(match(f2, "/test42/somefile/").params["ext"] == "")
  doAssert(match(f2, "/test42/somefile.txt").params["ext"] == "txt")
  doAssert(match(f2, "/test42/somefile.txt/").params["ext"] == "txt")
  
  let f3 = parsePattern(r"/test32/\@\\\??")
  doAssert(match(f3, r"/test32/@\").matched)
  doAssert(not match(f3, r"/test32/@\\").matched)
  doAssert(match(f3, r"/test32/@\?").matched)