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)
|