fix(go.sum): update ResolveSpec dependency to v1.0.87
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s

This commit is contained in:
Hein
2026-06-23 13:17:16 +02:00
parent 0227912325
commit 1adf50e3db
2436 changed files with 1078758 additions and 114 deletions
+25
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
}
}