Go: Goji patternマッチ部分のコードリーディング
Created at Sun, Nov 9, 2014Gojiでは, sinatraライクにURLマッチングをさせることが出来ます. この記事では, URLマッチングさせる部分のソースコードを解説していきます.
まず, parsePatternメソッドから.
func parsePattern(p interface{}) Pattern {
switch p.(type) {
case Pattern:
return p.(Pattern)
case *regexp.Regexp:
return parseRegexpPattern(p.(*regexp.Regexp))
case string:
return parseStringPattern(p.(string))
default:
log.Fatalf("Unknown pattern type %v. Expected a web.Pattern, "+
"regexp.Regexp, or a string.", p)
}
panic("log.Fatalf does not return")
}
switch p.(type)
で, 与えられたパターンの型によって処理を分岐させています.
Pattern, *regexp.Regexp, stringの3パターンに対応しています.
今回は, stringの場合, parseStringPattern
の処理を見ていきます.
parseStringPattern 中身
const bc = "/.;,"
var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`)
func parseStringPattern(s string) stringPattern {
raw := s
var wildcard bool
if strings.HasSuffix(s, "/*") {
s = s[:len(s)-1]
wildcard = true
}
matches := patternRe.FindAllStringSubmatchIndex(s, -1)
pats := make([]string, len(matches))
breaks := make([]byte, len(matches))
literals := make([]string, len(matches)+1)
n := 0
for i, match := range matches {
a, b := match[2], match[3]
literals[i] = s[n : a-1] // Need to leave off the colon
pats[i] = s[a:b]
if b == len(s) {
breaks[i] = '/'
} else {
breaks[i] = s[b]
}
n = b
}
literals[len(matches)] = s[n:]
return stringPattern{
raw: raw,
pats: pats,
breaks: breaks,
literals: literals,
wildcard: wildcard,
}
}
こんな感じです. 上から順番に見ていきます.
var wildcard bool
if strings.HasSuffix(s, "/*") {
s = s[:len(s)-1]
wildcard = true
}
ここでは, /*
で終わる場合, 末尾の*
を取り除いて, wildcardをtrueにしています.
Goでは, 初期値が与えられないと0値で初期化されるので var wildcard bool
の辞典ではfalseになります.
matches := patternRe.FindAllStringSubmatchIndex(s, -1)
FindAllStringSubmatchIndex
は, FindStringSubmatchIndex
の全文マッチ版になっています. FindStringSubmatchIndex
は, 一致する一番左のテキストのindexのペアと, subexpression(部分式)のindexのペアを返してくれます.
例えば, 下の例だと[1 5 2 4]
が返されます.
reg := regexp.MustCompile("h(og)e")
fmt.Println(reg.FindStringSubmatchIndex("ahogea"))
pats := make([]string, len(matches))
breaks := make([]byte, len(matches))
literals := make([]string, len(matches)+1)
n := 0
for i, match := range matches {
a, b := match[2], match[3]
literals[i] = s[n : a-1] // Need to leave off the colon
pats[i] = s[a:b]
if b == len(s) {
breaks[i] = '/'
} else {
breaks[i] = s[b]
}
n = b
}
ここでは, /:nameと書かれたパターンを抽出します. patsには, :で区切られたパターンが, literalsには, 固定されたURLリテラル, breaksにはliteralとpatternの区切り文字が入ります.
例です. /users/:userid
だと, literals{/users/}, breaks{/}, pats{userid}
にそれぞれなります. /users/:userid/like/:likeid
だと literals{/users/, /like/}, breaks{/, /}, pats{userid, loveid}
になります.