fix(go.sum): update ResolveSpec dependency to v1.0.87
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
Copyright (C) 2016, Kohei YOSHIDA <https://yosida95.com/>. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
uritemplate
|
||||
===========
|
||||
|
||||
`uritemplate`_ is a Go implementation of `URI Template`_ [RFC6570] with
|
||||
full functionality of URI Template Level 4.
|
||||
|
||||
uritemplate can also generate a regexp that matches expansion of the
|
||||
URI Template from a URI Template.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
Installation
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ go get -u github.com/yosida95/uritemplate/v3
|
||||
|
||||
Documentation
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The documentation is available on GoDoc_.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
See `examples on GoDoc`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
`uritemplate`_ is distributed under the BSD 3-Clause license.
|
||||
PLEASE READ ./LICENSE carefully and follow its clauses to use this software.
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
yosida95_
|
||||
|
||||
|
||||
.. _`URI Template`: https://tools.ietf.org/html/rfc6570
|
||||
.. _Godoc: https://godoc.org/github.com/yosida95/uritemplate
|
||||
.. _`examples on GoDoc`: https://godoc.org/github.com/yosida95/uritemplate#pkg-examples
|
||||
.. _yosida95: https://yosida95.com/
|
||||
.. _uritemplate: https://github.com/yosida95/uritemplate
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type compiler struct {
|
||||
prog *prog
|
||||
}
|
||||
|
||||
func (c *compiler) init() {
|
||||
c.prog = &prog{}
|
||||
}
|
||||
|
||||
func (c *compiler) op(opcode progOpcode) uint32 {
|
||||
i := len(c.prog.op)
|
||||
c.prog.op = append(c.prog.op, progOp{code: opcode})
|
||||
return uint32(i)
|
||||
}
|
||||
|
||||
func (c *compiler) opWithRune(opcode progOpcode, r rune) uint32 {
|
||||
addr := c.op(opcode)
|
||||
(&c.prog.op[addr]).r = r
|
||||
return addr
|
||||
}
|
||||
|
||||
func (c *compiler) opWithRuneClass(opcode progOpcode, rc runeClass) uint32 {
|
||||
addr := c.op(opcode)
|
||||
(&c.prog.op[addr]).rc = rc
|
||||
return addr
|
||||
}
|
||||
|
||||
func (c *compiler) opWithAddr(opcode progOpcode, absaddr uint32) uint32 {
|
||||
addr := c.op(opcode)
|
||||
(&c.prog.op[addr]).i = absaddr
|
||||
return addr
|
||||
}
|
||||
|
||||
func (c *compiler) opWithAddrDelta(opcode progOpcode, delta uint32) uint32 {
|
||||
return c.opWithAddr(opcode, uint32(len(c.prog.op))+delta)
|
||||
}
|
||||
|
||||
func (c *compiler) opWithName(opcode progOpcode, name string) uint32 {
|
||||
addr := c.op(opcode)
|
||||
(&c.prog.op[addr]).name = name
|
||||
return addr
|
||||
}
|
||||
|
||||
func (c *compiler) compileString(str string) {
|
||||
for i := 0; i < len(str); {
|
||||
// NOTE(yosida95): It is confirmed at parse time that literals
|
||||
// consist of only valid-UTF8 runes.
|
||||
r, size := utf8.DecodeRuneInString(str[i:])
|
||||
c.opWithRune(opRune, r)
|
||||
i += size
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) compileRuneClass(rc runeClass, maxlen int) {
|
||||
for i := 0; i < maxlen; i++ {
|
||||
if i > 0 {
|
||||
c.opWithAddrDelta(opSplit, 7)
|
||||
}
|
||||
c.opWithAddrDelta(opSplit, 3) // raw rune or pct-encoded
|
||||
c.opWithRuneClass(opRuneClass, rc) // raw rune
|
||||
c.opWithAddrDelta(opJmp, 4) //
|
||||
c.opWithRune(opRune, '%') // pct-encoded
|
||||
c.opWithRuneClass(opRuneClass, runeClassPctE) //
|
||||
c.opWithRuneClass(opRuneClass, runeClassPctE) //
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) compileRuneClassInfinite(rc runeClass) {
|
||||
start := c.opWithAddrDelta(opSplit, 3) // raw rune or pct-encoded
|
||||
c.opWithRuneClass(opRuneClass, rc) // raw rune
|
||||
c.opWithAddrDelta(opJmp, 4) //
|
||||
c.opWithRune(opRune, '%') // pct-encoded
|
||||
c.opWithRuneClass(opRuneClass, runeClassPctE) //
|
||||
c.opWithRuneClass(opRuneClass, runeClassPctE) //
|
||||
c.opWithAddrDelta(opSplit, 2) // loop
|
||||
c.opWithAddr(opJmp, start) //
|
||||
}
|
||||
|
||||
func (c *compiler) compileVarspecValue(spec varspec, expr *expression) {
|
||||
var specname string
|
||||
if spec.maxlen > 0 {
|
||||
specname = fmt.Sprintf("%s:%d", spec.name, spec.maxlen)
|
||||
} else {
|
||||
specname = spec.name
|
||||
}
|
||||
|
||||
c.prog.numCap++
|
||||
|
||||
c.opWithName(opCapStart, specname)
|
||||
|
||||
split := c.op(opSplit)
|
||||
if spec.maxlen > 0 {
|
||||
c.compileRuneClass(expr.allow, spec.maxlen)
|
||||
} else {
|
||||
c.compileRuneClassInfinite(expr.allow)
|
||||
}
|
||||
|
||||
capEnd := c.opWithName(opCapEnd, specname)
|
||||
c.prog.op[split].i = capEnd
|
||||
}
|
||||
|
||||
func (c *compiler) compileVarspec(spec varspec, expr *expression) {
|
||||
switch {
|
||||
case expr.named && spec.explode:
|
||||
split1 := c.op(opSplit)
|
||||
noop := c.op(opNoop)
|
||||
c.compileString(spec.name)
|
||||
|
||||
split2 := c.op(opSplit)
|
||||
c.opWithRune(opRune, '=')
|
||||
c.compileVarspecValue(spec, expr)
|
||||
|
||||
split3 := c.op(opSplit)
|
||||
c.compileString(expr.sep)
|
||||
c.opWithAddr(opJmp, noop)
|
||||
|
||||
c.prog.op[split2].i = uint32(len(c.prog.op))
|
||||
c.compileString(expr.ifemp)
|
||||
c.opWithAddr(opJmp, split3)
|
||||
|
||||
c.prog.op[split1].i = uint32(len(c.prog.op))
|
||||
c.prog.op[split3].i = uint32(len(c.prog.op))
|
||||
|
||||
case expr.named && !spec.explode:
|
||||
c.compileString(spec.name)
|
||||
|
||||
split2 := c.op(opSplit)
|
||||
c.opWithRune(opRune, '=')
|
||||
|
||||
split3 := c.op(opSplit)
|
||||
|
||||
split4 := c.op(opSplit)
|
||||
c.compileVarspecValue(spec, expr)
|
||||
|
||||
split5 := c.op(opSplit)
|
||||
c.prog.op[split4].i = split5
|
||||
c.compileString(",")
|
||||
c.opWithAddr(opJmp, split4)
|
||||
|
||||
c.prog.op[split3].i = uint32(len(c.prog.op))
|
||||
c.compileString(",")
|
||||
jmp1 := c.op(opJmp)
|
||||
|
||||
c.prog.op[split2].i = uint32(len(c.prog.op))
|
||||
c.compileString(expr.ifemp)
|
||||
|
||||
c.prog.op[split5].i = uint32(len(c.prog.op))
|
||||
c.prog.op[jmp1].i = uint32(len(c.prog.op))
|
||||
|
||||
case !expr.named:
|
||||
start := uint32(len(c.prog.op))
|
||||
c.compileVarspecValue(spec, expr)
|
||||
|
||||
split1 := c.op(opSplit)
|
||||
jmp := c.op(opJmp)
|
||||
|
||||
c.prog.op[split1].i = uint32(len(c.prog.op))
|
||||
if spec.explode {
|
||||
c.compileString(expr.sep)
|
||||
} else {
|
||||
c.opWithRune(opRune, ',')
|
||||
}
|
||||
c.opWithAddr(opJmp, start)
|
||||
|
||||
c.prog.op[jmp].i = uint32(len(c.prog.op))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) compileExpression(expr *expression) {
|
||||
if len(expr.vars) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
split1 := c.op(opSplit)
|
||||
c.compileString(expr.first)
|
||||
|
||||
for i, size := 0, len(expr.vars); i < size; i++ {
|
||||
spec := expr.vars[i]
|
||||
|
||||
split2 := c.op(opSplit)
|
||||
if i > 0 {
|
||||
split3 := c.op(opSplit)
|
||||
c.compileString(expr.sep)
|
||||
c.prog.op[split3].i = uint32(len(c.prog.op))
|
||||
}
|
||||
c.compileVarspec(spec, expr)
|
||||
c.prog.op[split2].i = uint32(len(c.prog.op))
|
||||
}
|
||||
|
||||
c.prog.op[split1].i = uint32(len(c.prog.op))
|
||||
}
|
||||
|
||||
func (c *compiler) compileLiterals(lt literals) {
|
||||
c.compileString(string(lt))
|
||||
}
|
||||
|
||||
func (c *compiler) compile(tmpl *Template) {
|
||||
c.op(opLineBegin)
|
||||
for i := range tmpl.exprs {
|
||||
expr := tmpl.exprs[i]
|
||||
switch expr := expr.(type) {
|
||||
default:
|
||||
panic("unhandled expression")
|
||||
case *expression:
|
||||
c.compileExpression(expr)
|
||||
case literals:
|
||||
c.compileLiterals(expr)
|
||||
}
|
||||
}
|
||||
c.op(opLineEnd)
|
||||
c.op(opEnd)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
type CompareFlags uint8
|
||||
|
||||
const (
|
||||
CompareVarname CompareFlags = 1 << iota
|
||||
)
|
||||
|
||||
// Equals reports whether or not two URI Templates t1 and t2 are equivalent.
|
||||
func Equals(t1 *Template, t2 *Template, flags CompareFlags) bool {
|
||||
if len(t1.exprs) != len(t2.exprs) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(t1.exprs); i++ {
|
||||
switch t1 := t1.exprs[i].(type) {
|
||||
case literals:
|
||||
t2, ok := t2.exprs[i].(literals)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if t1 != t2 {
|
||||
return false
|
||||
}
|
||||
case *expression:
|
||||
t2, ok := t2.exprs[i].(*expression)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if t1.op != t2.op || len(t1.vars) != len(t2.vars) {
|
||||
return false
|
||||
}
|
||||
for n := 0; n < len(t1.vars); n++ {
|
||||
v1 := t1.vars[n]
|
||||
v2 := t2.vars[n]
|
||||
if flags&CompareVarname == CompareVarname && v1.name != v2.name {
|
||||
return false
|
||||
}
|
||||
if v1.maxlen != v2.maxlen || v1.explode != v2.explode {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("unhandled case")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func errorf(pos int, format string, a ...interface{}) error {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
return fmt.Errorf("uritemplate:%d:%s", pos, msg)
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
hex = []byte("0123456789ABCDEF")
|
||||
// reserved = gen-delims / sub-delims
|
||||
// gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
|
||||
// sub-delims = "!" / "$" / "&" / "’" / "(" / ")"
|
||||
// / "*" / "+" / "," / ";" / "="
|
||||
rangeReserved = &unicode.RangeTable{
|
||||
R16: []unicode.Range16{
|
||||
{Lo: 0x21, Hi: 0x21, Stride: 1}, // '!'
|
||||
{Lo: 0x23, Hi: 0x24, Stride: 1}, // '#' - '$'
|
||||
{Lo: 0x26, Hi: 0x2C, Stride: 1}, // '&' - ','
|
||||
{Lo: 0x2F, Hi: 0x2F, Stride: 1}, // '/'
|
||||
{Lo: 0x3A, Hi: 0x3B, Stride: 1}, // ':' - ';'
|
||||
{Lo: 0x3D, Hi: 0x3D, Stride: 1}, // '='
|
||||
{Lo: 0x3F, Hi: 0x40, Stride: 1}, // '?' - '@'
|
||||
{Lo: 0x5B, Hi: 0x5B, Stride: 1}, // '['
|
||||
{Lo: 0x5D, Hi: 0x5D, Stride: 1}, // ']'
|
||||
},
|
||||
LatinOffset: 9,
|
||||
}
|
||||
reReserved = `\x21\x23\x24\x26-\x2c\x2f\x3a\x3b\x3d\x3f\x40\x5b\x5d`
|
||||
// ALPHA = %x41-5A / %x61-7A
|
||||
// DIGIT = %x30-39
|
||||
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
rangeUnreserved = &unicode.RangeTable{
|
||||
R16: []unicode.Range16{
|
||||
{Lo: 0x2D, Hi: 0x2E, Stride: 1}, // '-' - '.'
|
||||
{Lo: 0x30, Hi: 0x39, Stride: 1}, // '0' - '9'
|
||||
{Lo: 0x41, Hi: 0x5A, Stride: 1}, // 'A' - 'Z'
|
||||
{Lo: 0x5F, Hi: 0x5F, Stride: 1}, // '_'
|
||||
{Lo: 0x61, Hi: 0x7A, Stride: 1}, // 'a' - 'z'
|
||||
{Lo: 0x7E, Hi: 0x7E, Stride: 1}, // '~'
|
||||
},
|
||||
}
|
||||
reUnreserved = `\x2d\x2e\x30-\x39\x41-\x5a\x5f\x61-\x7a\x7e`
|
||||
)
|
||||
|
||||
type runeClass uint8
|
||||
|
||||
const (
|
||||
runeClassU runeClass = 1 << iota
|
||||
runeClassR
|
||||
runeClassPctE
|
||||
runeClassLast
|
||||
|
||||
runeClassUR = runeClassU | runeClassR
|
||||
)
|
||||
|
||||
var runeClassNames = []string{
|
||||
"U",
|
||||
"R",
|
||||
"pct-encoded",
|
||||
}
|
||||
|
||||
func (rc runeClass) String() string {
|
||||
ret := make([]string, 0, len(runeClassNames))
|
||||
for i, j := 0, runeClass(1); j < runeClassLast; j <<= 1 {
|
||||
if rc&j == j {
|
||||
ret = append(ret, runeClassNames[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
return strings.Join(ret, "+")
|
||||
}
|
||||
|
||||
func pctEncode(w *strings.Builder, r rune) {
|
||||
if s := r >> 24 & 0xff; s > 0 {
|
||||
w.Write([]byte{'%', hex[s/16], hex[s%16]})
|
||||
}
|
||||
if s := r >> 16 & 0xff; s > 0 {
|
||||
w.Write([]byte{'%', hex[s/16], hex[s%16]})
|
||||
}
|
||||
if s := r >> 8 & 0xff; s > 0 {
|
||||
w.Write([]byte{'%', hex[s/16], hex[s%16]})
|
||||
}
|
||||
if s := r & 0xff; s > 0 {
|
||||
w.Write([]byte{'%', hex[s/16], hex[s%16]})
|
||||
}
|
||||
}
|
||||
|
||||
func unhex(c byte) byte {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10
|
||||
case 'A' <= c && c <= 'F':
|
||||
return c - 'A' + 10
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func ishex(c byte) bool {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func pctDecode(s string) string {
|
||||
size := len(s)
|
||||
for i := 0; i < len(s); {
|
||||
switch s[i] {
|
||||
case '%':
|
||||
size -= 2
|
||||
i += 3
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
if size == len(s) {
|
||||
return s
|
||||
}
|
||||
|
||||
buf := make([]byte, size)
|
||||
j := 0
|
||||
for i := 0; i < len(s); {
|
||||
switch c := s[i]; c {
|
||||
case '%':
|
||||
buf[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
|
||||
i += 3
|
||||
j++
|
||||
default:
|
||||
buf[j] = c
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
type escapeFunc func(*strings.Builder, string) error
|
||||
|
||||
func escapeLiteral(w *strings.Builder, v string) error {
|
||||
w.WriteString(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func escapeExceptU(w *strings.Builder, v string) error {
|
||||
for i := 0; i < len(v); {
|
||||
r, size := utf8.DecodeRuneInString(v[i:])
|
||||
if r == utf8.RuneError {
|
||||
return errorf(i, "invalid encoding")
|
||||
}
|
||||
if unicode.Is(rangeUnreserved, r) {
|
||||
w.WriteRune(r)
|
||||
} else {
|
||||
pctEncode(w, r)
|
||||
}
|
||||
i += size
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func escapeExceptUR(w *strings.Builder, v string) error {
|
||||
for i := 0; i < len(v); {
|
||||
r, size := utf8.DecodeRuneInString(v[i:])
|
||||
if r == utf8.RuneError {
|
||||
return errorf(i, "invalid encoding")
|
||||
}
|
||||
// TODO(yosida95): is pct-encoded triplets allowed here?
|
||||
if unicode.In(r, rangeUnreserved, rangeReserved) {
|
||||
w.WriteRune(r)
|
||||
} else {
|
||||
pctEncode(w, r)
|
||||
}
|
||||
i += size
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type template interface {
|
||||
expand(*strings.Builder, Values) error
|
||||
regexp(*strings.Builder)
|
||||
}
|
||||
|
||||
type literals string
|
||||
|
||||
func (l literals) expand(b *strings.Builder, _ Values) error {
|
||||
b.WriteString(string(l))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l literals) regexp(b *strings.Builder) {
|
||||
b.WriteString("(?:")
|
||||
b.WriteString(regexp.QuoteMeta(string(l)))
|
||||
b.WriteByte(')')
|
||||
}
|
||||
|
||||
type varspec struct {
|
||||
name string
|
||||
maxlen int
|
||||
explode bool
|
||||
}
|
||||
|
||||
type expression struct {
|
||||
vars []varspec
|
||||
op parseOp
|
||||
first string
|
||||
sep string
|
||||
named bool
|
||||
ifemp string
|
||||
escape escapeFunc
|
||||
allow runeClass
|
||||
}
|
||||
|
||||
func (e *expression) init() {
|
||||
switch e.op {
|
||||
case parseOpSimple:
|
||||
e.sep = ","
|
||||
e.escape = escapeExceptU
|
||||
e.allow = runeClassU
|
||||
case parseOpPlus:
|
||||
e.sep = ","
|
||||
e.escape = escapeExceptUR
|
||||
e.allow = runeClassUR
|
||||
case parseOpCrosshatch:
|
||||
e.first = "#"
|
||||
e.sep = ","
|
||||
e.escape = escapeExceptUR
|
||||
e.allow = runeClassUR
|
||||
case parseOpDot:
|
||||
e.first = "."
|
||||
e.sep = "."
|
||||
e.escape = escapeExceptU
|
||||
e.allow = runeClassU
|
||||
case parseOpSlash:
|
||||
e.first = "/"
|
||||
e.sep = "/"
|
||||
e.escape = escapeExceptU
|
||||
e.allow = runeClassU
|
||||
case parseOpSemicolon:
|
||||
e.first = ";"
|
||||
e.sep = ";"
|
||||
e.named = true
|
||||
e.escape = escapeExceptU
|
||||
e.allow = runeClassU
|
||||
case parseOpQuestion:
|
||||
e.first = "?"
|
||||
e.sep = "&"
|
||||
e.named = true
|
||||
e.ifemp = "="
|
||||
e.escape = escapeExceptU
|
||||
e.allow = runeClassU
|
||||
case parseOpAmpersand:
|
||||
e.first = "&"
|
||||
e.sep = "&"
|
||||
e.named = true
|
||||
e.ifemp = "="
|
||||
e.escape = escapeExceptU
|
||||
e.allow = runeClassU
|
||||
}
|
||||
}
|
||||
|
||||
func (e *expression) expand(w *strings.Builder, values Values) error {
|
||||
first := true
|
||||
for _, varspec := range e.vars {
|
||||
value := values.Get(varspec.name)
|
||||
if !value.Valid() {
|
||||
continue
|
||||
}
|
||||
|
||||
if first {
|
||||
w.WriteString(e.first)
|
||||
first = false
|
||||
} else {
|
||||
w.WriteString(e.sep)
|
||||
}
|
||||
|
||||
if err := value.expand(w, varspec, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *expression) regexp(b *strings.Builder) {
|
||||
if e.first != "" {
|
||||
b.WriteString("(?:") // $1
|
||||
b.WriteString(regexp.QuoteMeta(e.first))
|
||||
}
|
||||
b.WriteByte('(') // $2
|
||||
runeClassToRegexp(b, e.allow, e.named || e.vars[0].explode)
|
||||
if len(e.vars) > 1 || e.vars[0].explode {
|
||||
max := len(e.vars) - 1
|
||||
for i := 0; i < len(e.vars); i++ {
|
||||
if e.vars[i].explode {
|
||||
max = -1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString("(?:") // $3
|
||||
b.WriteString(regexp.QuoteMeta(e.sep))
|
||||
runeClassToRegexp(b, e.allow, e.named || max < 0)
|
||||
b.WriteByte(')') // $3
|
||||
if max > 0 {
|
||||
b.WriteString("{0,")
|
||||
b.WriteString(strconv.Itoa(max))
|
||||
b.WriteByte('}')
|
||||
} else {
|
||||
b.WriteByte('*')
|
||||
}
|
||||
}
|
||||
b.WriteByte(')') // $2
|
||||
if e.first != "" {
|
||||
b.WriteByte(')') // $1
|
||||
}
|
||||
b.WriteByte('?')
|
||||
}
|
||||
|
||||
func runeClassToRegexp(b *strings.Builder, class runeClass, named bool) {
|
||||
b.WriteString("(?:(?:[")
|
||||
if class&runeClassR == 0 {
|
||||
b.WriteString(`\x2c`)
|
||||
if named {
|
||||
b.WriteString(`\x3d`)
|
||||
}
|
||||
}
|
||||
if class&runeClassU == runeClassU {
|
||||
b.WriteString(reUnreserved)
|
||||
}
|
||||
if class&runeClassR == runeClassR {
|
||||
b.WriteString(reReserved)
|
||||
}
|
||||
b.WriteString("]")
|
||||
b.WriteString("|%[[:xdigit:]][[:xdigit:]]")
|
||||
b.WriteString(")*)")
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
// threadList implements https://research.swtch.com/sparse.
|
||||
type threadList struct {
|
||||
dense []threadEntry
|
||||
sparse []uint32
|
||||
}
|
||||
|
||||
type threadEntry struct {
|
||||
pc uint32
|
||||
t *thread
|
||||
}
|
||||
|
||||
type thread struct {
|
||||
op *progOp
|
||||
cap map[string][]int
|
||||
}
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type matcher struct {
|
||||
prog *prog
|
||||
|
||||
list1 threadList
|
||||
list2 threadList
|
||||
matched bool
|
||||
cap map[string][]int
|
||||
|
||||
input string
|
||||
}
|
||||
|
||||
func (m *matcher) at(pos int) (rune, int, bool) {
|
||||
if l := len(m.input); pos < l {
|
||||
c := m.input[pos]
|
||||
if c < utf8.RuneSelf {
|
||||
return rune(c), 1, pos+1 < l
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(m.input[pos:])
|
||||
return r, size, pos+size < l
|
||||
}
|
||||
return -1, 0, false
|
||||
}
|
||||
|
||||
func (m *matcher) add(list *threadList, pc uint32, pos int, next bool, cap map[string][]int) {
|
||||
if i := list.sparse[pc]; i < uint32(len(list.dense)) && list.dense[i].pc == pc {
|
||||
return
|
||||
}
|
||||
|
||||
n := len(list.dense)
|
||||
list.dense = list.dense[:n+1]
|
||||
list.sparse[pc] = uint32(n)
|
||||
|
||||
e := &list.dense[n]
|
||||
e.pc = pc
|
||||
e.t = nil
|
||||
|
||||
op := &m.prog.op[pc]
|
||||
switch op.code {
|
||||
default:
|
||||
panic("unhandled opcode")
|
||||
case opRune, opRuneClass, opEnd:
|
||||
e.t = &thread{
|
||||
op: &m.prog.op[pc],
|
||||
cap: make(map[string][]int, len(m.cap)),
|
||||
}
|
||||
for k, v := range cap {
|
||||
e.t.cap[k] = make([]int, len(v))
|
||||
copy(e.t.cap[k], v)
|
||||
}
|
||||
case opLineBegin:
|
||||
if pos == 0 {
|
||||
m.add(list, pc+1, pos, next, cap)
|
||||
}
|
||||
case opLineEnd:
|
||||
if !next {
|
||||
m.add(list, pc+1, pos, next, cap)
|
||||
}
|
||||
case opCapStart, opCapEnd:
|
||||
ocap := make(map[string][]int, len(m.cap))
|
||||
for k, v := range cap {
|
||||
ocap[k] = make([]int, len(v))
|
||||
copy(ocap[k], v)
|
||||
}
|
||||
ocap[op.name] = append(ocap[op.name], pos)
|
||||
m.add(list, pc+1, pos, next, ocap)
|
||||
case opSplit:
|
||||
m.add(list, pc+1, pos, next, cap)
|
||||
m.add(list, op.i, pos, next, cap)
|
||||
case opJmp:
|
||||
m.add(list, op.i, pos, next, cap)
|
||||
case opJmpIfNotDefined:
|
||||
m.add(list, pc+1, pos, next, cap)
|
||||
m.add(list, op.i, pos, next, cap)
|
||||
case opJmpIfNotFirst:
|
||||
m.add(list, pc+1, pos, next, cap)
|
||||
m.add(list, op.i, pos, next, cap)
|
||||
case opJmpIfNotEmpty:
|
||||
m.add(list, op.i, pos, next, cap)
|
||||
m.add(list, pc+1, pos, next, cap)
|
||||
case opNoop:
|
||||
m.add(list, pc+1, pos, next, cap)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *matcher) step(clist *threadList, nlist *threadList, r rune, pos int, nextPos int, next bool) {
|
||||
debug.Printf("===== %q =====", string(r))
|
||||
for i := 0; i < len(clist.dense); i++ {
|
||||
e := clist.dense[i]
|
||||
if debug {
|
||||
var buf bytes.Buffer
|
||||
dumpProg(&buf, m.prog, e.pc)
|
||||
debug.Printf("\n%s", buf.String())
|
||||
}
|
||||
if e.t == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
t := e.t
|
||||
op := t.op
|
||||
switch op.code {
|
||||
default:
|
||||
panic("unhandled opcode")
|
||||
case opRune:
|
||||
if op.r == r {
|
||||
m.add(nlist, e.pc+1, nextPos, next, t.cap)
|
||||
}
|
||||
case opRuneClass:
|
||||
ret := false
|
||||
if !ret && op.rc&runeClassU == runeClassU {
|
||||
ret = ret || unicode.Is(rangeUnreserved, r)
|
||||
}
|
||||
if !ret && op.rc&runeClassR == runeClassR {
|
||||
ret = ret || unicode.Is(rangeReserved, r)
|
||||
}
|
||||
if !ret && op.rc&runeClassPctE == runeClassPctE {
|
||||
ret = ret || unicode.Is(unicode.ASCII_Hex_Digit, r)
|
||||
}
|
||||
if ret {
|
||||
m.add(nlist, e.pc+1, nextPos, next, t.cap)
|
||||
}
|
||||
case opEnd:
|
||||
m.matched = true
|
||||
for k, v := range t.cap {
|
||||
m.cap[k] = make([]int, len(v))
|
||||
copy(m.cap[k], v)
|
||||
}
|
||||
clist.dense = clist.dense[:0]
|
||||
}
|
||||
}
|
||||
clist.dense = clist.dense[:0]
|
||||
}
|
||||
|
||||
func (m *matcher) match() bool {
|
||||
pos := 0
|
||||
clist, nlist := &m.list1, &m.list2
|
||||
for {
|
||||
if len(clist.dense) == 0 && m.matched {
|
||||
break
|
||||
}
|
||||
r, width, next := m.at(pos)
|
||||
if !m.matched {
|
||||
m.add(clist, 0, pos, next, m.cap)
|
||||
}
|
||||
m.step(clist, nlist, r, pos, pos+width, next)
|
||||
|
||||
if width < 1 {
|
||||
break
|
||||
}
|
||||
pos += width
|
||||
|
||||
clist, nlist = nlist, clist
|
||||
}
|
||||
return m.matched
|
||||
}
|
||||
|
||||
func (tmpl *Template) Match(expansion string) Values {
|
||||
tmpl.mu.Lock()
|
||||
if tmpl.prog == nil {
|
||||
c := compiler{}
|
||||
c.init()
|
||||
c.compile(tmpl)
|
||||
tmpl.prog = c.prog
|
||||
}
|
||||
prog := tmpl.prog
|
||||
tmpl.mu.Unlock()
|
||||
|
||||
n := len(prog.op)
|
||||
m := matcher{
|
||||
prog: prog,
|
||||
list1: threadList{
|
||||
dense: make([]threadEntry, 0, n),
|
||||
sparse: make([]uint32, n),
|
||||
},
|
||||
list2: threadList{
|
||||
dense: make([]threadEntry, 0, n),
|
||||
sparse: make([]uint32, n),
|
||||
},
|
||||
cap: make(map[string][]int, prog.numCap),
|
||||
input: expansion,
|
||||
}
|
||||
if !m.match() {
|
||||
return nil
|
||||
}
|
||||
|
||||
match := make(Values, len(m.cap))
|
||||
for name, indices := range m.cap {
|
||||
v := Value{V: make([]string, len(indices)/2)}
|
||||
for i := range v.V {
|
||||
v.V[i] = pctDecode(expansion[indices[2*i]:indices[2*i+1]])
|
||||
}
|
||||
if len(v.V) == 1 {
|
||||
v.T = ValueTypeString
|
||||
} else {
|
||||
v.T = ValueTypeList
|
||||
}
|
||||
match[name] = v
|
||||
}
|
||||
return match
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type parseOp int
|
||||
|
||||
const (
|
||||
parseOpSimple parseOp = iota
|
||||
parseOpPlus
|
||||
parseOpCrosshatch
|
||||
parseOpDot
|
||||
parseOpSlash
|
||||
parseOpSemicolon
|
||||
parseOpQuestion
|
||||
parseOpAmpersand
|
||||
)
|
||||
|
||||
var (
|
||||
rangeVarchar = &unicode.RangeTable{
|
||||
R16: []unicode.Range16{
|
||||
{Lo: 0x0030, Hi: 0x0039, Stride: 1}, // '0' - '9'
|
||||
{Lo: 0x0041, Hi: 0x005A, Stride: 1}, // 'A' - 'Z'
|
||||
{Lo: 0x005F, Hi: 0x005F, Stride: 1}, // '_'
|
||||
{Lo: 0x0061, Hi: 0x007A, Stride: 1}, // 'a' - 'z'
|
||||
},
|
||||
LatinOffset: 4,
|
||||
}
|
||||
rangeLiterals = &unicode.RangeTable{
|
||||
R16: []unicode.Range16{
|
||||
{Lo: 0x0021, Hi: 0x0021, Stride: 1}, // '!'
|
||||
{Lo: 0x0023, Hi: 0x0024, Stride: 1}, // '#' - '$'
|
||||
{Lo: 0x0026, Hi: 0x003B, Stride: 1}, // '&' ''' '(' - ';'. '''/27 used to be excluded but an errata is in the review process https://www.rfc-editor.org/errata/eid6937
|
||||
{Lo: 0x003D, Hi: 0x003D, Stride: 1}, // '='
|
||||
{Lo: 0x003F, Hi: 0x005B, Stride: 1}, // '?' - '['
|
||||
{Lo: 0x005D, Hi: 0x005D, Stride: 1}, // ']'
|
||||
{Lo: 0x005F, Hi: 0x005F, Stride: 1}, // '_'
|
||||
{Lo: 0x0061, Hi: 0x007A, Stride: 1}, // 'a' - 'z'
|
||||
{Lo: 0x007E, Hi: 0x007E, Stride: 1}, // '~'
|
||||
{Lo: 0x00A0, Hi: 0xD7FF, Stride: 1}, // ucschar
|
||||
{Lo: 0xE000, Hi: 0xF8FF, Stride: 1}, // iprivate
|
||||
{Lo: 0xF900, Hi: 0xFDCF, Stride: 1}, // ucschar
|
||||
{Lo: 0xFDF0, Hi: 0xFFEF, Stride: 1}, // ucschar
|
||||
},
|
||||
R32: []unicode.Range32{
|
||||
{Lo: 0x00010000, Hi: 0x0001FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x00020000, Hi: 0x0002FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x00030000, Hi: 0x0003FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x00040000, Hi: 0x0004FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x00050000, Hi: 0x0005FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x00060000, Hi: 0x0006FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x00070000, Hi: 0x0007FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x00080000, Hi: 0x0008FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x00090000, Hi: 0x0009FFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x000A0000, Hi: 0x000AFFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x000B0000, Hi: 0x000BFFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x000C0000, Hi: 0x000CFFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x000D0000, Hi: 0x000DFFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x000E1000, Hi: 0x000EFFFD, Stride: 1}, // ucschar
|
||||
{Lo: 0x000F0000, Hi: 0x000FFFFD, Stride: 1}, // iprivate
|
||||
{Lo: 0x00100000, Hi: 0x0010FFFD, Stride: 1}, // iprivate
|
||||
},
|
||||
LatinOffset: 10,
|
||||
}
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
r string
|
||||
start int
|
||||
stop int
|
||||
state parseState
|
||||
}
|
||||
|
||||
func (p *parser) errorf(i rune, format string, a ...interface{}) error {
|
||||
return fmt.Errorf("%s: %s%s", fmt.Sprintf(format, a...), p.r[0:p.stop], string(i))
|
||||
}
|
||||
|
||||
func (p *parser) rune() (rune, int) {
|
||||
r, size := utf8.DecodeRuneInString(p.r[p.stop:])
|
||||
if r != utf8.RuneError {
|
||||
p.stop += size
|
||||
}
|
||||
return r, size
|
||||
}
|
||||
|
||||
func (p *parser) unread(r rune) {
|
||||
p.stop -= utf8.RuneLen(r)
|
||||
}
|
||||
|
||||
type parseState int
|
||||
|
||||
const (
|
||||
parseStateDefault = parseState(iota)
|
||||
parseStateOperator
|
||||
parseStateVarList
|
||||
parseStateVarName
|
||||
parseStatePrefix
|
||||
)
|
||||
|
||||
func (p *parser) setState(state parseState) {
|
||||
p.state = state
|
||||
p.start = p.stop
|
||||
}
|
||||
|
||||
func (p *parser) parseURITemplate() (*Template, error) {
|
||||
tmpl := Template{
|
||||
raw: p.r,
|
||||
exprs: []template{},
|
||||
}
|
||||
|
||||
var exp *expression
|
||||
for {
|
||||
r, size := p.rune()
|
||||
if r == utf8.RuneError {
|
||||
if size == 0 {
|
||||
if p.state != parseStateDefault {
|
||||
return nil, p.errorf('_', "incomplete expression")
|
||||
}
|
||||
if p.start < p.stop {
|
||||
tmpl.exprs = append(tmpl.exprs, literals(p.r[p.start:p.stop]))
|
||||
}
|
||||
return &tmpl, nil
|
||||
}
|
||||
return nil, p.errorf('_', "invalid UTF-8 sequence")
|
||||
}
|
||||
|
||||
switch p.state {
|
||||
case parseStateDefault:
|
||||
switch r {
|
||||
case '{':
|
||||
if stop := p.stop - size; stop > p.start {
|
||||
tmpl.exprs = append(tmpl.exprs, literals(p.r[p.start:stop]))
|
||||
}
|
||||
exp = &expression{}
|
||||
tmpl.exprs = append(tmpl.exprs, exp)
|
||||
p.setState(parseStateOperator)
|
||||
case '%':
|
||||
p.unread(r)
|
||||
if err := p.consumeTriplet(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
if !unicode.Is(rangeLiterals, r) {
|
||||
p.unread(r)
|
||||
return nil, p.errorf('_', "unacceptable character (hint: use %%XX encoding)")
|
||||
}
|
||||
}
|
||||
case parseStateOperator:
|
||||
switch r {
|
||||
default:
|
||||
p.unread(r)
|
||||
exp.op = parseOpSimple
|
||||
case '+':
|
||||
exp.op = parseOpPlus
|
||||
case '#':
|
||||
exp.op = parseOpCrosshatch
|
||||
case '.':
|
||||
exp.op = parseOpDot
|
||||
case '/':
|
||||
exp.op = parseOpSlash
|
||||
case ';':
|
||||
exp.op = parseOpSemicolon
|
||||
case '?':
|
||||
exp.op = parseOpQuestion
|
||||
case '&':
|
||||
exp.op = parseOpAmpersand
|
||||
case '=', ',', '!', '@', '|': // op-reserved
|
||||
return nil, p.errorf('|', "unimplemented operator (op-reserved)")
|
||||
}
|
||||
p.setState(parseStateVarName)
|
||||
case parseStateVarList:
|
||||
switch r {
|
||||
case ',':
|
||||
p.setState(parseStateVarName)
|
||||
case '}':
|
||||
exp.init()
|
||||
p.setState(parseStateDefault)
|
||||
default:
|
||||
p.unread(r)
|
||||
return nil, p.errorf('_', "unrecognized value modifier")
|
||||
}
|
||||
case parseStateVarName:
|
||||
switch r {
|
||||
case ':', '*':
|
||||
name := p.r[p.start : p.stop-size]
|
||||
if !isValidVarname(name) {
|
||||
return nil, p.errorf('|', "unacceptable variable name")
|
||||
}
|
||||
explode := r == '*'
|
||||
exp.vars = append(exp.vars, varspec{
|
||||
name: name,
|
||||
explode: explode,
|
||||
})
|
||||
if explode {
|
||||
p.setState(parseStateVarList)
|
||||
} else {
|
||||
p.setState(parseStatePrefix)
|
||||
}
|
||||
case ',', '}':
|
||||
p.unread(r)
|
||||
name := p.r[p.start:p.stop]
|
||||
if !isValidVarname(name) {
|
||||
return nil, p.errorf('|', "unacceptable variable name")
|
||||
}
|
||||
exp.vars = append(exp.vars, varspec{
|
||||
name: name,
|
||||
})
|
||||
p.setState(parseStateVarList)
|
||||
case '%':
|
||||
p.unread(r)
|
||||
if err := p.consumeTriplet(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '.':
|
||||
if dot := p.stop - size; dot == p.start || p.r[dot-1] == '.' {
|
||||
return nil, p.errorf('|', "unacceptable variable name")
|
||||
}
|
||||
default:
|
||||
if !unicode.Is(rangeVarchar, r) {
|
||||
p.unread(r)
|
||||
return nil, p.errorf('_', "unacceptable variable name")
|
||||
}
|
||||
}
|
||||
case parseStatePrefix:
|
||||
spec := &(exp.vars[len(exp.vars)-1])
|
||||
switch {
|
||||
case '0' <= r && r <= '9':
|
||||
spec.maxlen *= 10
|
||||
spec.maxlen += int(r - '0')
|
||||
if spec.maxlen == 0 || spec.maxlen > 9999 {
|
||||
return nil, p.errorf('|', "max-length must be (0, 9999]")
|
||||
}
|
||||
default:
|
||||
p.unread(r)
|
||||
if spec.maxlen == 0 {
|
||||
return nil, p.errorf('_', "max-length must be (0, 9999]")
|
||||
}
|
||||
p.setState(parseStateVarList)
|
||||
}
|
||||
default:
|
||||
p.unread(r)
|
||||
panic(p.errorf('_', "unhandled parseState(%d)", p.state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isValidVarname(name string) bool {
|
||||
if l := len(name); l == 0 || name[0] == '.' || name[l-1] == '.' {
|
||||
return false
|
||||
}
|
||||
for i := 1; i < len(name)-1; i++ {
|
||||
switch c := name[i]; c {
|
||||
case '.':
|
||||
if name[i-1] == '.' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *parser) consumeTriplet() error {
|
||||
if len(p.r)-p.stop < 3 || p.r[p.stop] != '%' || !ishex(p.r[p.stop+1]) || !ishex(p.r[p.stop+2]) {
|
||||
return p.errorf('_', "incomplete pct-encodeed")
|
||||
}
|
||||
p.stop += 3
|
||||
return nil
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type progOpcode uint16
|
||||
|
||||
const (
|
||||
// match
|
||||
opRune progOpcode = iota
|
||||
opRuneClass
|
||||
opLineBegin
|
||||
opLineEnd
|
||||
// capture
|
||||
opCapStart
|
||||
opCapEnd
|
||||
// stack
|
||||
opSplit
|
||||
opJmp
|
||||
opJmpIfNotDefined
|
||||
opJmpIfNotEmpty
|
||||
opJmpIfNotFirst
|
||||
// result
|
||||
opEnd
|
||||
// fake
|
||||
opNoop
|
||||
opcodeMax
|
||||
)
|
||||
|
||||
var opcodeNames = []string{
|
||||
// match
|
||||
"opRune",
|
||||
"opRuneClass",
|
||||
"opLineBegin",
|
||||
"opLineEnd",
|
||||
// capture
|
||||
"opCapStart",
|
||||
"opCapEnd",
|
||||
// stack
|
||||
"opSplit",
|
||||
"opJmp",
|
||||
"opJmpIfNotDefined",
|
||||
"opJmpIfNotEmpty",
|
||||
"opJmpIfNotFirst",
|
||||
// result
|
||||
"opEnd",
|
||||
}
|
||||
|
||||
func (code progOpcode) String() string {
|
||||
if code >= opcodeMax {
|
||||
return ""
|
||||
}
|
||||
return opcodeNames[code]
|
||||
}
|
||||
|
||||
type progOp struct {
|
||||
code progOpcode
|
||||
r rune
|
||||
rc runeClass
|
||||
i uint32
|
||||
|
||||
name string
|
||||
}
|
||||
|
||||
func dumpProgOp(b *bytes.Buffer, op *progOp) {
|
||||
b.WriteString(op.code.String())
|
||||
switch op.code {
|
||||
case opRune:
|
||||
b.WriteString("(")
|
||||
b.WriteString(strconv.QuoteToASCII(string(op.r)))
|
||||
b.WriteString(")")
|
||||
case opRuneClass:
|
||||
b.WriteString("(")
|
||||
b.WriteString(op.rc.String())
|
||||
b.WriteString(")")
|
||||
case opCapStart, opCapEnd:
|
||||
b.WriteString("(")
|
||||
b.WriteString(strconv.QuoteToASCII(op.name))
|
||||
b.WriteString(")")
|
||||
case opSplit:
|
||||
b.WriteString(" -> ")
|
||||
b.WriteString(strconv.FormatInt(int64(op.i), 10))
|
||||
case opJmp, opJmpIfNotFirst:
|
||||
b.WriteString(" -> ")
|
||||
b.WriteString(strconv.FormatInt(int64(op.i), 10))
|
||||
case opJmpIfNotDefined, opJmpIfNotEmpty:
|
||||
b.WriteString("(")
|
||||
b.WriteString(strconv.QuoteToASCII(op.name))
|
||||
b.WriteString(")")
|
||||
b.WriteString(" -> ")
|
||||
b.WriteString(strconv.FormatInt(int64(op.i), 10))
|
||||
}
|
||||
}
|
||||
|
||||
type prog struct {
|
||||
op []progOp
|
||||
numCap int
|
||||
}
|
||||
|
||||
func dumpProg(b *bytes.Buffer, prog *prog, pc uint32) {
|
||||
for i := range prog.op {
|
||||
op := prog.op[i]
|
||||
|
||||
pos := strconv.Itoa(i)
|
||||
if uint32(i) == pc {
|
||||
pos = "*" + pos
|
||||
}
|
||||
b.WriteString(" "[len(pos):])
|
||||
b.WriteString(pos)
|
||||
|
||||
b.WriteByte('\t')
|
||||
dumpProgOp(b, &op)
|
||||
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
func (p *prog) String() string {
|
||||
b := bytes.Buffer{}
|
||||
dumpProg(&b, p, 0)
|
||||
return b.String()
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import (
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = debugT(false)
|
||||
)
|
||||
|
||||
type debugT bool
|
||||
|
||||
func (t debugT) Printf(format string, v ...interface{}) {
|
||||
if t {
|
||||
log.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Template represents a URI Template.
|
||||
type Template struct {
|
||||
raw string
|
||||
exprs []template
|
||||
|
||||
// protects the rest of fields
|
||||
mu sync.Mutex
|
||||
varnames []string
|
||||
re *regexp.Regexp
|
||||
prog *prog
|
||||
}
|
||||
|
||||
// New parses and constructs a new Template instance based on the template.
|
||||
// New returns an error if the template cannot be recognized.
|
||||
func New(template string) (*Template, error) {
|
||||
return (&parser{r: template}).parseURITemplate()
|
||||
}
|
||||
|
||||
// MustNew panics if the template cannot be recognized.
|
||||
func MustNew(template string) *Template {
|
||||
ret, err := New(template)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Raw returns a raw URI template passed to New in string.
|
||||
func (t *Template) Raw() string {
|
||||
return t.raw
|
||||
}
|
||||
|
||||
// Varnames returns variable names used in the template.
|
||||
func (t *Template) Varnames() []string {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.varnames != nil {
|
||||
return t.varnames
|
||||
}
|
||||
|
||||
reg := map[string]struct{}{}
|
||||
t.varnames = []string{}
|
||||
for i := range t.exprs {
|
||||
expr, ok := t.exprs[i].(*expression)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, spec := range expr.vars {
|
||||
if _, ok := reg[spec.name]; ok {
|
||||
continue
|
||||
}
|
||||
reg[spec.name] = struct{}{}
|
||||
t.varnames = append(t.varnames, spec.name)
|
||||
}
|
||||
}
|
||||
|
||||
return t.varnames
|
||||
}
|
||||
|
||||
// Expand returns a URI reference corresponding to the template expanded using the passed variables.
|
||||
func (t *Template) Expand(vars Values) (string, error) {
|
||||
var w strings.Builder
|
||||
for i := range t.exprs {
|
||||
expr := t.exprs[i]
|
||||
if err := expr.expand(&w, vars); err != nil {
|
||||
return w.String(), err
|
||||
}
|
||||
}
|
||||
return w.String(), nil
|
||||
}
|
||||
|
||||
// Regexp converts the template to regexp and returns compiled *regexp.Regexp.
|
||||
func (t *Template) Regexp() *regexp.Regexp {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.re != nil {
|
||||
return t.re
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteByte('^')
|
||||
for _, expr := range t.exprs {
|
||||
expr.regexp(&b)
|
||||
}
|
||||
b.WriteByte('$')
|
||||
t.re = regexp.MustCompile(b.String())
|
||||
|
||||
return t.re
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of The BSD 3-Clause License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package uritemplate
|
||||
|
||||
import "strings"
|
||||
|
||||
// A varname containing pct-encoded characters is not the same variable as
|
||||
// a varname with those same characters decoded.
|
||||
//
|
||||
// -- https://tools.ietf.org/html/rfc6570#section-2.3
|
||||
type Values map[string]Value
|
||||
|
||||
func (v Values) Set(name string, value Value) {
|
||||
v[name] = value
|
||||
}
|
||||
|
||||
func (v Values) Get(name string) Value {
|
||||
if v == nil {
|
||||
return Value{}
|
||||
}
|
||||
return v[name]
|
||||
}
|
||||
|
||||
type ValueType uint8
|
||||
|
||||
const (
|
||||
ValueTypeString = iota
|
||||
ValueTypeList
|
||||
ValueTypeKV
|
||||
valueTypeLast
|
||||
)
|
||||
|
||||
var valueTypeNames = []string{
|
||||
"String",
|
||||
"List",
|
||||
"KV",
|
||||
}
|
||||
|
||||
func (vt ValueType) String() string {
|
||||
if vt < valueTypeLast {
|
||||
return valueTypeNames[vt]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
T ValueType
|
||||
V []string
|
||||
}
|
||||
|
||||
func (v Value) String() string {
|
||||
if v.Valid() && v.T == ValueTypeString {
|
||||
return v.V[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v Value) List() []string {
|
||||
if v.Valid() && v.T == ValueTypeList {
|
||||
return v.V
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Value) KV() []string {
|
||||
if v.Valid() && v.T == ValueTypeKV {
|
||||
return v.V
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Value) Valid() bool {
|
||||
switch v.T {
|
||||
default:
|
||||
return false
|
||||
case ValueTypeString:
|
||||
return len(v.V) > 0
|
||||
case ValueTypeList:
|
||||
return len(v.V) > 0
|
||||
case ValueTypeKV:
|
||||
return len(v.V) > 0 && len(v.V)%2 == 0
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) expand(w *strings.Builder, spec varspec, exp *expression) error {
|
||||
switch v.T {
|
||||
case ValueTypeString:
|
||||
val := v.V[0]
|
||||
var maxlen int
|
||||
if max := len(val); spec.maxlen < 1 || spec.maxlen > max {
|
||||
maxlen = max
|
||||
} else {
|
||||
maxlen = spec.maxlen
|
||||
}
|
||||
|
||||
if exp.named {
|
||||
w.WriteString(spec.name)
|
||||
if val == "" {
|
||||
w.WriteString(exp.ifemp)
|
||||
return nil
|
||||
}
|
||||
w.WriteByte('=')
|
||||
}
|
||||
return exp.escape(w, val[:maxlen])
|
||||
case ValueTypeList:
|
||||
var sep string
|
||||
if spec.explode {
|
||||
sep = exp.sep
|
||||
} else {
|
||||
sep = ","
|
||||
}
|
||||
|
||||
var pre string
|
||||
var preifemp string
|
||||
if spec.explode && exp.named {
|
||||
pre = spec.name + "="
|
||||
preifemp = spec.name + exp.ifemp
|
||||
}
|
||||
|
||||
if !spec.explode && exp.named {
|
||||
w.WriteString(spec.name)
|
||||
w.WriteByte('=')
|
||||
}
|
||||
for i := range v.V {
|
||||
val := v.V[i]
|
||||
if i > 0 {
|
||||
w.WriteString(sep)
|
||||
}
|
||||
if val == "" {
|
||||
w.WriteString(preifemp)
|
||||
continue
|
||||
}
|
||||
w.WriteString(pre)
|
||||
|
||||
if err := exp.escape(w, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case ValueTypeKV:
|
||||
var sep string
|
||||
var kvsep string
|
||||
if spec.explode {
|
||||
sep = exp.sep
|
||||
kvsep = "="
|
||||
} else {
|
||||
sep = ","
|
||||
kvsep = ","
|
||||
}
|
||||
|
||||
var ifemp string
|
||||
var kescape escapeFunc
|
||||
if spec.explode && exp.named {
|
||||
ifemp = exp.ifemp
|
||||
kescape = escapeLiteral
|
||||
} else {
|
||||
ifemp = ","
|
||||
kescape = exp.escape
|
||||
}
|
||||
|
||||
if !spec.explode && exp.named {
|
||||
w.WriteString(spec.name)
|
||||
w.WriteByte('=')
|
||||
}
|
||||
|
||||
for i := 0; i < len(v.V); i += 2 {
|
||||
if i > 0 {
|
||||
w.WriteString(sep)
|
||||
}
|
||||
if err := kescape(w, v.V[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
if v.V[i+1] == "" {
|
||||
w.WriteString(ifemp)
|
||||
continue
|
||||
}
|
||||
w.WriteString(kvsep)
|
||||
|
||||
if err := exp.escape(w, v.V[i+1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns Value that represents string.
|
||||
func String(v string) Value {
|
||||
return Value{
|
||||
T: ValueTypeString,
|
||||
V: []string{v},
|
||||
}
|
||||
}
|
||||
|
||||
// List returns Value that represents list.
|
||||
func List(v ...string) Value {
|
||||
return Value{
|
||||
T: ValueTypeList,
|
||||
V: v,
|
||||
}
|
||||
}
|
||||
|
||||
// KV returns Value that represents associative list.
|
||||
// KV panics if len(kv) is not even.
|
||||
func KV(kv ...string) Value {
|
||||
if len(kv)%2 != 0 {
|
||||
panic("uritemplate.go: count of the kv must be even number")
|
||||
}
|
||||
return Value{
|
||||
T: ValueTypeKV,
|
||||
V: kv,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user