pax_global_header00006660000000000000000000000064140064724760014524gustar00rootroot0000000000000052 comment=2147fb31205921d022818561da850209b44b0097 tagparser-2.0.0/000077500000000000000000000000001400647247600135135ustar00rootroot00000000000000tagparser-2.0.0/.travis.yml000066400000000000000000000004771400647247600156340ustar00rootroot00000000000000dist: xenial language: go go: - 1.14.x - 1.15.x - tip matrix: allow_failures: - go: tip env: - GO111MODULE=on go_import_path: github.com/vmihailenco/tagparser before_install: - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 tagparser-2.0.0/LICENSE000066400000000000000000000024551400647247600145260ustar00rootroot00000000000000Copyright (c) 2019 The github.com/vmihailenco/tagparser Authors. 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. 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 OWNER 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. tagparser-2.0.0/Makefile000066400000000000000000000003401400647247600151500ustar00rootroot00000000000000all: go test ./... go test ./... -short -race go test ./... -run=NONE -bench=. -benchmem env GOOS=linux GOARCH=386 go test ./... go vet ./... go get github.com/gordonklaus/ineffassign ineffassign . golangci-lint run tagparser-2.0.0/README.md000066400000000000000000000011261400647247600147720ustar00rootroot00000000000000# Opinionated Golang tag parser [![Build Status](https://travis-ci.org/vmihailenco/tagparser.png?branch=master)](https://travis-ci.org/vmihailenco/tagparser) [![GoDoc](https://godoc.org/github.com/vmihailenco/tagparser?status.svg)](https://godoc.org/github.com/vmihailenco/tagparser) ## Installation Install: ```shell go get github.com/vmihailenco/tagparser/v2 ``` ## Quickstart ```go func ExampleParse() { tag := tagparser.Parse("some_name,key:value,key2:'complex value'") fmt.Println(tag.Name) fmt.Println(tag.Options) // Output: some_name // map[key:value key2:'complex value'] } ``` tagparser-2.0.0/example_test.go000066400000000000000000000004341400647247600165350ustar00rootroot00000000000000package tagparser_test import ( "fmt" "github.com/vmihailenco/tagparser/v2" ) func ExampleParse() { tag := tagparser.Parse("some_name,key:value,key2:'complex value'") fmt.Println(tag.Name) fmt.Println(tag.Options) // Output: some_name // map[key:value key2:complex value] } tagparser-2.0.0/go.mod000066400000000000000000000000641400647247600146210ustar00rootroot00000000000000module github.com/vmihailenco/tagparser/v2 go 1.15 tagparser-2.0.0/internal/000077500000000000000000000000001400647247600153275ustar00rootroot00000000000000tagparser-2.0.0/internal/parser/000077500000000000000000000000001400647247600166235ustar00rootroot00000000000000tagparser-2.0.0/internal/parser/parser.go000066400000000000000000000021561400647247600204520ustar00rootroot00000000000000package parser import ( "bytes" "github.com/vmihailenco/tagparser/v2/internal" ) type Parser struct { b []byte i int } func New(b []byte) *Parser { return &Parser{ b: b, } } func NewString(s string) *Parser { return New(internal.StringToBytes(s)) } func (p *Parser) Bytes() []byte { return p.b[p.i:] } func (p *Parser) Valid() bool { return p.i < len(p.b) } func (p *Parser) Read() byte { if p.Valid() { c := p.b[p.i] p.Advance() return c } return 0 } func (p *Parser) Peek() byte { if p.Valid() { return p.b[p.i] } return 0 } func (p *Parser) Advance() { p.i++ } func (p *Parser) Skip(skip byte) bool { if p.Peek() == skip { p.Advance() return true } return false } func (p *Parser) SkipBytes(skip []byte) bool { if len(skip) > len(p.b[p.i:]) { return false } if !bytes.Equal(p.b[p.i:p.i+len(skip)], skip) { return false } p.i += len(skip) return true } func (p *Parser) ReadSep(sep byte) ([]byte, bool) { ind := bytes.IndexByte(p.b[p.i:], sep) if ind == -1 { b := p.b[p.i:] p.i = len(p.b) return b, false } b := p.b[p.i : p.i+ind] p.i += ind + 1 return b, true } tagparser-2.0.0/internal/safe.go000066400000000000000000000002341400647247600165730ustar00rootroot00000000000000// +build appengine package internal func BytesToString(b []byte) string { return string(b) } func StringToBytes(s string) []byte { return []byte(s) } tagparser-2.0.0/internal/unsafe.go000066400000000000000000000005561400647247600171450ustar00rootroot00000000000000// +build !appengine package internal import ( "unsafe" ) // BytesToString converts byte slice to string. func BytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } // StringToBytes converts string to byte slice. func StringToBytes(s string) []byte { return *(*[]byte)(unsafe.Pointer( &struct { string Cap int }{s, len(s)}, )) } tagparser-2.0.0/tagparser.go000066400000000000000000000047031400647247600160360ustar00rootroot00000000000000package tagparser import ( "strings" "github.com/vmihailenco/tagparser/v2/internal/parser" ) type Tag struct { Name string Options map[string]string } func (t *Tag) HasOption(name string) bool { _, ok := t.Options[name] return ok } func Parse(s string) *Tag { p := &tagParser{ Parser: parser.NewString(s), } p.parseKey() return &p.Tag } type tagParser struct { *parser.Parser Tag Tag hasName bool key string } func (p *tagParser) setTagOption(key, value string) { key = strings.TrimSpace(key) value = strings.TrimSpace(value) if !p.hasName { p.hasName = true if key == "" { p.Tag.Name = value return } } if p.Tag.Options == nil { p.Tag.Options = make(map[string]string) } if key == "" { p.Tag.Options[value] = "" } else { p.Tag.Options[key] = value } } func (p *tagParser) parseKey() { p.key = "" var b []byte for p.Valid() { c := p.Read() switch c { case ',': p.Skip(' ') p.setTagOption("", string(b)) p.parseKey() return case ':': p.key = string(b) p.parseValue() return case '\'': p.parseQuotedValue() return default: b = append(b, c) } } if len(b) > 0 { p.setTagOption("", string(b)) } } func (p *tagParser) parseValue() { const quote = '\'' c := p.Peek() if c == quote { p.Skip(quote) p.parseQuotedValue() return } var b []byte for p.Valid() { c = p.Read() switch c { case '\\': b = append(b, p.Read()) case '(': b = append(b, c) b = p.readBrackets(b) case ',': p.Skip(' ') p.setTagOption(p.key, string(b)) p.parseKey() return default: b = append(b, c) } } p.setTagOption(p.key, string(b)) } func (p *tagParser) readBrackets(b []byte) []byte { var lvl int loop: for p.Valid() { c := p.Read() switch c { case '\\': b = append(b, p.Read()) case '(': b = append(b, c) lvl++ case ')': b = append(b, c) lvl-- if lvl < 0 { break loop } default: b = append(b, c) } } return b } func (p *tagParser) parseQuotedValue() { const quote = '\'' var b []byte for p.Valid() { bb, ok := p.ReadSep(quote) if !ok { b = append(b, bb...) break } // keep the escaped single-quote, and continue until we've found the // one that isn't. if len(bb) > 0 && bb[len(bb)-1] == '\\' { b = append(b, bb[:len(bb)-1]...) b = append(b, quote) continue } b = append(b, bb...) break } p.setTagOption(p.key, string(b)) if p.Skip(',') { p.Skip(' ') } p.parseKey() } tagparser-2.0.0/tagparser_test.go000066400000000000000000000035151400647247600170750ustar00rootroot00000000000000package tagparser_test import ( "testing" "github.com/vmihailenco/tagparser/v2" ) var tagTests = []struct { tag string name string opts map[string]string }{ {"", "", nil}, {"hello", "hello", nil}, {"hello,world", "hello", map[string]string{"world": ""}}, {"'hello,world'", "hello,world", nil}, {"'hello:world'", "hello:world", nil}, {",hello", "", map[string]string{"hello": ""}}, {",hello,world", "", map[string]string{"hello": "", "world": ""}}, {"hello:", "", map[string]string{"hello": ""}}, {"hello:world", "", map[string]string{"hello": "world"}}, {"hello:world,foo", "", map[string]string{"hello": "world", "foo": ""}}, {"hello:world,foo:bar", "", map[string]string{"hello": "world", "foo": "bar"}}, {"hello:'world1,world2'", "", map[string]string{"hello": "world1,world2"}}, {"hello:'world1,world2',world3", "", map[string]string{"hello": "world1,world2", "world3": ""}}, {"hello:'world1:world2',world3", "", map[string]string{"hello": "world1:world2", "world3": ""}}, {`hello:'D\'Angelo, esquire', foo:bar`, "", map[string]string{"hello": "D'Angelo, esquire", "foo": "bar"}}, {"hello:world('foo', 'bar')", "", map[string]string{"hello": "world('foo', 'bar')"}}, {" hello, foo: bar ", "hello", map[string]string{"foo": "bar"}}, } func TestTagParser(t *testing.T) { for _, test := range tagTests { tag := tagparser.Parse(test.tag) if tag.Name != test.name { t.Fatalf("got %q, wanted %q (tag=%q)", tag.Name, test.name, test.tag) } if len(tag.Options) != len(test.opts) { t.Fatalf( "got %#v options, wanted %#v (tag=%q)", tag.Options, test.opts, test.tag, ) } for k, v := range test.opts { s, ok := tag.Options[k] if !ok { t.Fatalf("option=%q does not exist (tag=%q)", k, test.tag) } if s != v { t.Fatalf("got %s=%q, wanted %q (tag=%q)", k, tag.Options[k], v, test.tag) } } } }