pax_global_header00006660000000000000000000000064134245652730014525gustar00rootroot0000000000000052 comment=b4df798d65426f8c8ab5ca5f9987aec5575d26c9 parth-2.0.1/000077500000000000000000000000001342456527300126435ustar00rootroot00000000000000parth-2.0.1/LICENSE000066400000000000000000000020651342456527300136530ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2018 codemodus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. parth-2.0.1/README.md000066400000000000000000000127261342456527300141320ustar00rootroot00000000000000# parth go get -u github.com/h2non/parth Package parth provides path parsing for segment unmarshaling and slicing. In other words, parth provides simple and flexible access to (URL) path parameters. Along with string, all basic non-alias types are supported. An interface is available for implementation by user-defined types. When handling an int, uint, or float of any size, the first valid value within the specified segment will be used. ## Usage ```go Variables func Segment(path string, i int, v interface{}) error func Sequent(path, key string, v interface{}) error func Span(path string, i, j int) (string, error) func SubSeg(path, key string, i int, v interface{}) error func SubSpan(path, key string, i, j int) (string, error) type Parth func New(path string) *Parth func NewBySpan(path string, i, j int) *Parth func NewBySubSpan(path, key string, i, j int) *Parth func (p *Parth) Err() error func (p *Parth) Segment(i int, v interface{}) func (p *Parth) Sequent(key string, v interface{}) func (p *Parth) Span(i, j int) string func (p *Parth) SubSeg(key string, i int, v interface{}) func (p *Parth) SubSpan(key string, i, j int) string type Unmarshaler ``` ### Setup ("By Index") ```go import ( "fmt" "github.com/codemodus/parth" ) func handler(w http.ResponseWriter, r *http.Request) { var s string if err := parth.Segment(r.URL.Path, 4, &s); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v (%T)\n", s, s) // Output: // /some/path/things/42/others/3 // others (string) } ``` ### Setup ("By Key") ```go import ( "fmt" "github.com/codemodus/parth" ) func handler(w http.ResponseWriter, r *http.Request) { var i int64 if err := parth.Sequent(r.URL.Path, "things", &i); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v (%T)\n", i, i) // Output: // /some/path/things/42/others/3 // 42 (int64) } ``` ### Setup (Parth Type) ```go import ( "fmt" "github.com/codemodus/parth" ) func handler(w http.ResponseWriter, r *http.Request) { var s string var f float32 p := parth.New(r.URL.Path) p.Segment(2, &s) p.SubSeg("key", 1, &f) if err := p.Err(); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v (%T)\n", s, s) fmt.Printf("%v (%T)\n", f, f) // Output: // /zero/one/two/key/four/5.5/six // two (string) // 5.5 (float32) } ``` ### Setup (Unmarshaler) ```go import ( "fmt" "github.com/codemodus/parth" ) func handler(w http.ResponseWriter, r *http.Request) { /* type mytype []byte func (m *mytype) UnmarshalSegment(seg string) error { *m = []byte(seg) } */ var m mytype if err := parth.Segment(r.URL.Path, 4, &m); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v == %q (%T)\n", m, m, m) // Output: // /zero/one/two/key/four/5.5/six // [102 111 117 114] == "four" (mypkg.mytype) } ``` ## More Info ### Keep Using http.HandlerFunc And Minimize context.Context Usage The most obvious use case for parth is when working with any URL path such as the one found at http.Request.URL.Path. parth is fast enough that it can be used multiple times in place of a single use of similar router-parameter schemes or even context.Context. There is no need to use an alternate http handler function definition in order to pass data that is already being passed. The http.Request type already holds URL data and parth is great at handling it. Additionally, parth takes care of parsing selected path segments into the types actually needed. Parth not only does more, it's usually faster and less intrusive than the alternatives. ### Indexes If an index is negative, the negative count begins with the last segment. Providing a 0 for the second index is a special case which acts as an alias for the end of the path. An error is returned if: 1. Any index is out of range of the path; 2. When there are two indexes, the first index does not precede the second index. ### Keys If a key is involved, functions will only handle the portion of the path subsequent to the provided key. An error is returned if the key cannot be found in the path. ### First Whole, First Decimal (Restated - Important!) When handling an int, uint, or float of any size, the first valid value within the specified segment will be used. ## Documentation View the [GoDoc](http://godoc.org/github.com/codemodus/parth) ## Benchmarks Go 1.11 benchmark iter time/iter bytes alloc allocs --------- ---- --------- ----------- ------ BenchmarkSegmentString-8 30000000 39.60 ns/op 0 B/op 0 allocs/op BenchmarkSegmentInt-8 20000000 65.60 ns/op 0 B/op 0 allocs/op BenchmarkSegmentIntNegIndex-8 20000000 86.60 ns/op 0 B/op 0 allocs/op BenchmarkSpan-8 100000000 18.20 ns/op 0 B/op 0 allocs/op BenchmarkStdlibSegmentString-8 5000000 454.00 ns/op 50 B/op 2 allocs/op BenchmarkStdlibSegmentInt-8 3000000 526.00 ns/op 50 B/op 2 allocs/op BenchmarkStdlibSpan-8 3000000 518.00 ns/op 69 B/op 2 allocs/op BenchmarkContextLookupSetGet-8 1000000 1984.00 ns/op 480 B/op 6 allocs/op parth-2.0.1/benchmark_test.go000066400000000000000000000053221342456527300161650ustar00rootroot00000000000000package parth import ( "context" "fmt" "net/http" "path" "strconv" "strings" "testing" ) var ( x interface{} ) func BenchmarkSegmentString(b *testing.B) { p := "/zero/1/2" var r string b.ResetTimer() for n := 0; n < b.N; n++ { _ = Segment(p, 1, &r) } x = r } func BenchmarkSegmentInt(b *testing.B) { p := "/zero/1" var r int b.ResetTimer() for n := 0; n < b.N; n++ { _ = Segment(p, 1, &r) } x = r } func BenchmarkSegmentIntNegIndex(b *testing.B) { p := "/zero/1" var r int b.ResetTimer() for n := 0; n < b.N; n++ { _ = Segment(p, -1, &r) } x = r } func BenchmarkSpan(b *testing.B) { p := "/zero/1/2" var r string b.ResetTimer() for n := 0; n < b.N; n++ { r, _ = Span(p, 0, 1) } x = r } func BenchmarkStdlibSegmentString(b *testing.B) { p := "/zero/1" var r string b.ResetTimer() for n := 0; n < b.N; n++ { r, _ = stdlibSegmentString(p, 1) } x = r } func BenchmarkStdlibSegmentInt(b *testing.B) { p := "/zero/1" var r int b.ResetTimer() for n := 0; n < b.N; n++ { r, _ = stdlibSegmentInt(p, 1) } x = r } func BenchmarkStdlibSpan(b *testing.B) { p := "/zero/1/2" var r string b.ResetTimer() for n := 0; n < b.N; n++ { r, _ = stdlibSpan(p, 0, 1) } x = r } type testkey string var idKey testkey = "id" type param struct { key string value string } type params []param func (ps params) byName(name string) string { for i := range ps { if ps[i].key == name { return ps[i].value } } return "" } func makeParams(val string) params { return params{ {key: string(idKey), value: val}, } } func BenchmarkContextLookupSetGet(b *testing.B) { req, _ := http.NewRequest("GET", "", nil) ps := makeParams("123") var v, r0 string b.ResetTimer() for n := 0; n < b.N; n++ { v = ps.byName(string(idKey)) ctx := context.WithValue(req.Context(), idKey, v) req = req.WithContext(ctx) r0 = req.Context().Value(idKey).(string) } x = r0 } func stdlibSegmentString(p string, i int) (string, error) { s, err := stdlibSpan(p, i, i+1) if err != nil { return "", err } if s[0] == '/' { s = s[1:] } return s, nil } func stdlibSegmentInt(p string, i int) (int, error) { s, err := stdlibSegmentString(p, i) if err != nil { return 0, err } v, err := strconv.ParseInt(s, 10, 0) if err != nil { return 0, err } return int(v), nil } func stdlibSpan(p string, i, j int) (string, error) { cs := strings.Split(p, "/") pfx := "/" start := 1 if p[0] != '/' { start = 0 if i == 0 { pfx = "" } } cs = cs[start:] if len(cs) == 0 || i >= len(cs) || j > len(cs) || i < 0 || j <= 0 { return "", fmt.Errorf("segment out of bounds") } if i > j { return "", fmt.Errorf("segments reversed") } return pfx + path.Join(cs[i:j]...), nil } parth-2.0.1/example_test.go000066400000000000000000000052051342456527300156660ustar00rootroot00000000000000package parth_test import ( "fmt" "net/http" "os" "github.com/h2non/parth" ) var ( r, rErr = http.NewRequest("GET", "/zero/1/2/key/nn4.4nn/5.5", nil) ) func init() { if rErr != nil { panic(rErr) } } func Example() { var s string if err := parth.Segment(r.URL.Path, 4, &s); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v (%T)\n", s, s) // Output: // /zero/1/2/key/nn4.4nn/5.5 // nn4.4nn (string) } func ExampleSegment() { var s string if err := parth.Segment(r.URL.Path, 4, &s); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v (%T)\n", s, s) // Output: // /zero/1/2/key/nn4.4nn/5.5 // nn4.4nn (string) } func ExampleSequent() { var f float32 if err := parth.Sequent(r.URL.Path, "key", &f); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v (%T)\n", f, f) // Output: // /zero/1/2/key/nn4.4nn/5.5 // 4.4 (float32) } func ExampleSpan() { s, err := parth.Span(r.URL.Path, 2, 4) if err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Println(s) // Output: // /zero/1/2/key/nn4.4nn/5.5 // /2/key } func ExampleSubSeg() { var f float64 if err := parth.SubSeg(r.URL.Path, "key", 1, &f); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v (%T)\n", f, f) // Output: // /zero/1/2/key/nn4.4nn/5.5 // 5.5 (float64) } func ExampleSubSpan() { s0, err := parth.SubSpan(r.URL.Path, "zero", 2, 4) if err != nil { fmt.Fprintln(os.Stderr, err) } s1, err := parth.SubSpan(r.URL.Path, "1", 1, 3) if err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Println(s0) fmt.Println(s1) // Output: // /zero/1/2/key/nn4.4nn/5.5 // /key/nn4.4nn // /key/nn4.4nn } func ExampleParth() { var s string var f float32 p := parth.New(r.URL.Path) p.Segment(0, &s) p.SubSeg("key", 1, &f) if err := p.Err(); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v (%T)\n", s, s) fmt.Printf("%v (%T)\n", f, f) // Output: // /zero/1/2/key/nn4.4nn/5.5 // zero (string) // 5.5 (float32) } func ExampleUnmarshaler() { /* type mytype []byte func (m *mytype) UnmarshalSegment(seg string) error { *m = []byte(seg) } */ var m mytype if err := parth.Segment(r.URL.Path, 4, &m); err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Println(r.URL.Path) fmt.Printf("%v == %q (%T)\n", m, m, m) // Output: // /zero/1/2/key/nn4.4nn/5.5 // [110 110 52 46 52 110 110] == "nn4.4nn" (parth_test.mytype) } type mytype []byte func (m *mytype) UnmarshalSegment(seg string) error { *m = []byte(seg) return nil } parth-2.0.1/go.mod000066400000000000000000000000361342456527300137500ustar00rootroot00000000000000module github.com/h2non/parth parth-2.0.1/parth.go000066400000000000000000000201711342456527300143110ustar00rootroot00000000000000// Package parth provides path parsing for segment unmarshaling and slicing. In // other words, parth provides simple and flexible access to (URL) path // parameters. // // Along with string, all basic non-alias types are supported. An interface is // available for implementation by user-defined types. When handling an int, // uint, or float of any size, the first valid value within the specified // segment will be used. package parth import ( "errors" ) // Unmarshaler is the interface implemented by types that can unmarshal a path // segment representation of themselves. It is safe to assume that the segment // data will not include slashes. type Unmarshaler interface { UnmarshalSegment(string) error } // Err{Name} values facilitate error identification. var ( ErrUnknownType = errors.New("unknown type provided") ErrFirstSegNotFound = errors.New("first segment not found by index") ErrLastSegNotFound = errors.New("last segment not found by index") ErrSegOrderReversed = errors.New("first segment must precede last segment") ErrKeySegNotFound = errors.New("segment not found by key") ErrDataUnparsable = errors.New("data cannot be parsed") ) // Segment locates the path segment indicated by the index i and unmarshals it // into the provided type v. If the index is negative, the negative count // begins with the last segment. An error is returned if: 1. The type is not a // pointer to an instance of one of the basic non-alias types and does not // implement the Unmarshaler interface; 2. The index is out of range of the // path; 3. The located path segment data cannot be parsed as the provided type // or if an error is returned when using a provided Unmarshaler implementation. func Segment(path string, i int, v interface{}) error { //nolint var err error switch v := v.(type) { case *bool: *v, err = segmentToBool(path, i) case *float32: var f float64 f, err = segmentToFloatN(path, i, 32) *v = float32(f) case *float64: *v, err = segmentToFloatN(path, i, 64) case *int: var n int64 n, err = segmentToIntN(path, i, 0) *v = int(n) case *int16: var n int64 n, err = segmentToIntN(path, i, 16) *v = int16(n) case *int32: var n int64 n, err = segmentToIntN(path, i, 32) *v = int32(n) case *int64: *v, err = segmentToIntN(path, i, 64) case *int8: var n int64 n, err = segmentToIntN(path, i, 8) *v = int8(n) case *string: *v, err = segmentToString(path, i) case *uint: var n uint64 n, err = segmentToUintN(path, i, 0) *v = uint(n) case *uint16: var n uint64 n, err = segmentToUintN(path, i, 16) *v = uint16(n) case *uint32: var n uint64 n, err = segmentToUintN(path, i, 32) *v = uint32(n) case *uint64: *v, err = segmentToUintN(path, i, 64) case *uint8: var n uint64 n, err = segmentToUintN(path, i, 8) *v = uint8(n) case Unmarshaler: var s string s, err = segmentToString(path, i) if err == nil { err = v.UnmarshalSegment(s) } default: err = ErrUnknownType } return err } // Sequent is similar to Segment, but uses a key to locate a segment and then // unmarshal the subsequent segment. It is a simple wrapper over SubSeg with an // index of 0. func Sequent(path, key string, v interface{}) error { return SubSeg(path, key, 0, v) } // Span returns the path segments between two segment indexes i and j including // the first segment. If an index is negative, the negative count begins with // the last segment. Providing a 0 for the last index j is a special case which // acts as an alias for the end of the path. If the first segment does not begin // with a slash and it is part of the requested span, no slash will be added. An // error is returned if: 1. Either index is out of range of the path; 2. The // first index i does not precede the last index j. func Span(path string, i, j int) (string, error) { var f, l int var ok bool if i < 0 { f, ok = segStartIndexFromEnd(path, i) } else { f, ok = segStartIndexFromStart(path, i) } if !ok { return "", ErrFirstSegNotFound } if j > 0 { l, ok = segEndIndexFromStart(path, j) } else { l, ok = segEndIndexFromEnd(path, j) } if !ok { return "", ErrLastSegNotFound } if f == l { return "", nil } if f > l { return "", ErrSegOrderReversed } return path[f:l], nil } // SubSeg is similar to Segment, but only handles the portion of the path // subsequent to the provided key. For example, to access the segment // immediately after a key, an index of 0 should be provided (see Sequent). An // error is returned if the key cannot be found in the path. func SubSeg(path, key string, i int, v interface{}) error { //nolint var err error switch v := v.(type) { case *bool: *v, err = subSegToBool(path, key, i) case *float32: var f float64 f, err = subSegToFloatN(path, key, i, 32) *v = float32(f) case *float64: *v, err = subSegToFloatN(path, key, i, 64) case *int: var n int64 n, err = subSegToIntN(path, key, i, 0) *v = int(n) case *int16: var n int64 n, err = subSegToIntN(path, key, i, 16) *v = int16(n) case *int32: var n int64 n, err = subSegToIntN(path, key, i, 32) *v = int32(n) case *int64: *v, err = subSegToIntN(path, key, i, 64) case *int8: var n int64 n, err = subSegToIntN(path, key, i, 8) *v = int8(n) case *string: *v, err = subSegToString(path, key, i) case *uint: var n uint64 n, err = subSegToUintN(path, key, i, 0) *v = uint(n) case *uint16: var n uint64 n, err = subSegToUintN(path, key, i, 16) *v = uint16(n) case *uint32: var n uint64 n, err = subSegToUintN(path, key, i, 32) *v = uint32(n) case *uint64: *v, err = subSegToUintN(path, key, i, 64) case *uint8: var n uint64 n, err = subSegToUintN(path, key, i, 8) *v = uint8(n) case Unmarshaler: var s string s, err = subSegToString(path, key, i) if err == nil { err = v.UnmarshalSegment(s) } default: err = ErrUnknownType } return err } // SubSpan is similar to Span, but only handles the portion of the path // subsequent to the provided key. An error is returned if the key cannot be // found in the path. func SubSpan(path, key string, i, j int) (string, error) { si, ok := segIndexByKey(path, key) if !ok { return "", ErrKeySegNotFound } if i >= 0 { i++ } if j > 0 { j++ } s, err := Span(path[si:], i, j) if err != nil { return "", err } return s, nil } // Parth manages path and error data for processing a single path multiple // times while error checking only once. Only the first encountered error is // stored as all subsequent calls to Parth methods that can error are elided. type Parth struct { path string err error } // New constructs a pointer to an instance of Parth around the provided path. func New(path string) *Parth { return &Parth{path: path} } // NewBySpan constructs a pointer to an instance of Parth after preprocessing // the provided path with Span. func NewBySpan(path string, i, j int) *Parth { s, err := Span(path, i, j) return &Parth{s, err} } // NewBySubSpan constructs a pointer to an instance of Parth after // preprocessing the provided path with SubSpan. func NewBySubSpan(path, key string, i, j int) *Parth { s, err := SubSpan(path, key, i, j) return &Parth{s, err} } // Err returns the first error encountered by the *Parth receiver. func (p *Parth) Err() error { return p.err } // Segment operates the same as the package-level function Segment. func (p *Parth) Segment(i int, v interface{}) { if p.err != nil { return } p.err = Segment(p.path, i, v) } // Sequent operates the same as the package-level function Sequent. func (p *Parth) Sequent(key string, v interface{}) { p.SubSeg(key, 0, v) } // Span operates the same as the package-level function Span. func (p *Parth) Span(i, j int) string { if p.err != nil { return "" } s, err := Span(p.path, i, j) p.err = err return s } // SubSeg operates the same as the package-level function SubSeg. func (p *Parth) SubSeg(key string, i int, v interface{}) { if p.err != nil { return } p.err = SubSeg(p.path, key, i, v) } // SubSpan operates the same as the package-level function SubSpan. func (p *Parth) SubSpan(key string, i, j int) string { if p.err != nil { return "" } s, err := SubSpan(p.path, key, i, j) p.err = err return s } parth-2.0.1/parth_test.go000066400000000000000000000303301342456527300153460ustar00rootroot00000000000000package parth import ( "fmt" "reflect" "testing" ) func TestBhvrSegment(t *testing.T) { path := "/junk/4/key/true/other/3.3/" key := "" t.Run("bool", applyToBoolTFunc(path, key, pti(3), true)) t.Run("float32", applyToFloat32TFunc(path, key, pti(5), 3.3)) t.Run("float64", applyToFloat64TFunc(path, key, pti(5), 3.3)) t.Run("int", applyToIntTFunc(path, key, pti(1), 4)) t.Run("int16", applyToInt16TFunc(path, key, pti(1), 4)) t.Run("int32", applyToInt32TFunc(path, key, pti(1), 4)) t.Run("int64", applyToInt64TFunc(path, key, pti(1), 4)) t.Run("int8", applyToInt8TFunc(path, key, pti(1), 4)) t.Run("string", applyToStringTFunc(path, key, pti(0), "junk")) t.Run("uint", applyToUintTFunc(path, key, pti(-1), 3)) t.Run("uint16", applyToUint16TFunc(path, key, pti(-1), 3)) t.Run("uint32", applyToUint32TFunc(path, key, pti(-1), 3)) t.Run("uint64", applyToUint64TFunc(path, key, pti(-1), 3)) t.Run("uint8", applyToUint8TFunc(path, key, pti(-1), 3)) t.Run("unmarsaler", applyToUnmarshalerTFunc(path, key, pti(2), []byte("key"))) t.Run("badType", func(t *testing.T) { var x uintptr err := Segment(path, 3, &x) exp(t, t.Name(), err) }) } func TestBhvrSequent(t *testing.T) { path := "/junk/4/key/true/other/3.3/" var i *int t.Run("bool", applyToBoolTFunc(path, "key", i, true)) t.Run("float32", applyToFloat32TFunc(path, "other", i, 3.3)) t.Run("float64", applyToFloat64TFunc(path, "other", i, 3.3)) t.Run("int", applyToIntTFunc(path, "junk", i, 4)) t.Run("int16", applyToInt16TFunc(path, "junk", i, 4)) t.Run("int32", applyToInt32TFunc(path, "junk", i, 4)) t.Run("int64", applyToInt64TFunc(path, "junk", i, 4)) t.Run("int8", applyToInt8TFunc(path, "junk", i, 4)) t.Run("string", applyToStringTFunc(path, "key", i, "true")) t.Run("uint", applyToUintTFunc(path, "junk", i, 4)) t.Run("uint16", applyToUint16TFunc(path, "junk", i, 4)) t.Run("uint32", applyToUint32TFunc(path, "junk", i, 4)) t.Run("uint64", applyToUint64TFunc(path, "junk", i, 4)) t.Run("uint8", applyToUint8TFunc(path, "junk", i, 4)) t.Run("unmarsaler", applyToUnmarshalerTFunc(path, "key", i, []byte("true"))) t.Run("badType", func(t *testing.T) { var x uintptr err := Sequent(path, "key", &x) exp(t, t.Name(), err) }) } func TestBhvrSpan(t *testing.T) { path := "/zero/one/two/three/four" tests := []struct { name string path string i, j int want string ck checkFunc }{ {"5 segs: +1,+3", path, 1, 3, "/one/two", unx}, {"5 segs: +1,-2", path, 1, -2, "/one/two", unx}, {"5 segs: +1,00", path, 1, 0, "/one/two/three/four", unx}, {"5 segs: -3,-1", path, -3, -1, "/two/three", unx}, {"5 segs: -3,00", path, -3, 0, "/two/three/four", unx}, {"5 segs: -9,00", path, -9, 0, "", exp}, {"5 segs: 00,+9", path, 0, 9, "", exp}, {"3 no /: 00,+9", "zero/one/two", 0, 2, "zero/one", unx}, } for _, tt := range tests { got, err := Span(tt.path, tt.i, tt.j) if tt.ck(t, tt.name, err) { continue } if got != tt.want { t.Errorf(gwxFmt, tt.name, got, tt.want) } } } func TestBhvrSubSeg(t *testing.T) { path := "/junk/4/key/true/other/3.3/" t.Run("bool", applyToBoolTFunc(path, "junk", pti(2), true)) t.Run("float32", applyToFloat32TFunc(path, "true", pti(1), 3.3)) t.Run("float64", applyToFloat64TFunc(path, "true", pti(1), 3.3)) t.Run("int", applyToIntTFunc(path, "junk", pti(0), 4)) t.Run("int16", applyToInt16TFunc(path, "junk", pti(0), 4)) t.Run("int32", applyToInt32TFunc(path, "junk", pti(0), 4)) t.Run("int64", applyToInt64TFunc(path, "junk", pti(0), 4)) t.Run("int8", applyToInt8TFunc(path, "junk", pti(0), 4)) t.Run("string", applyToStringTFunc(path, "junk", pti(3), "other")) t.Run("uint", applyToUintTFunc(path, "junk", pti(0), 4)) t.Run("uint16", applyToUint16TFunc(path, "junk", pti(0), 4)) t.Run("uint32", applyToUint32TFunc(path, "junk", pti(0), 4)) t.Run("uint64", applyToUint64TFunc(path, "junk", pti(0), 4)) t.Run("uint8", applyToUint8TFunc(path, "junk", pti(0), 4)) t.Run("unmarsaler", applyToUnmarshalerTFunc(path, "key", pti(1), []byte("other"))) t.Run("badType", func(t *testing.T) { var x uintptr err := SubSeg(path, "key", 2, &x) exp(t, t.Name(), err) }) } func TestBhvrSubSpan(t *testing.T) { path := "/zero/one/two/key/four/five/six" tests := []struct { name string path string key string i, j int want string ck checkFunc }{ {"7 segs: 00,+2", path, "key", 0, 2, "/four/five", unx}, {"7 segs: +1,-1", path, "key", 1, -1, "/five", unx}, {"7 segs: +1,00", path, "key", 1, 0, "/five/six", unx}, {"7 segs: -3,-1", path, "key", -3, -1, "/four/five", unx}, {"7 segs: -3,00", path, "key", -3, 0, "/four/five/six", unx}, {"7 segs: -9,00", path, "key", -9, 0, "", exp}, {"7 segs: 00,+9", path, "key", 0, 9, "", exp}, } for _, tt := range tests { got, err := SubSpan(tt.path, tt.key, tt.i, tt.j) if tt.ck(t, tt.name, err) { continue } if got != tt.want { t.Errorf(gwxFmt, tt.name, got, tt.want) } } } func TestBhvrParth(t *testing.T) { t.Run("bySpan/segment", func(t *testing.T) { p := NewBySpan("/zero/one/two/three", 1, 3) var got string p.Segment(1, &got) if unx(t, t.Name(), p.Err()) { return } want := "two" if got != want { t.Errorf(gwFmt, got, want) } }) t.Run("bySubSpan/sequent", func(t *testing.T) { p := NewBySubSpan("/zero/one/two/three/four", "one", 1, 0) var got string p.Sequent("three", &got) if unx(t, t.Name(), p.Err()) { return } want := "four" if got != want { t.Errorf(gwFmt, got, want) } }) t.Run("basic", func(t *testing.T) { p := New("/zero/one/two/three") t.Run("subSeg", func(t *testing.T) { var got string p.SubSeg("one", 1, &got) want := "three" if got != want { t.Errorf(gwFmt, got, want) } }) t.Run("span", func(t *testing.T) { got := p.Span(1, 3) want := "/one/two" if got != want { t.Errorf(gwFmt, got, want) } }) t.Run("subSpan", func(t *testing.T) { got := p.SubSpan("one", 0, 0) want := "/two/three" if got != want { t.Errorf(gwFmt, got, want) } }) unx(t, t.Name(), p.Err()) }) } func segSeqSubSeg(path, key string, i *int, v interface{}) error { if path != "" && key != "" && i != nil { return SubSeg(path, key, *i, v) } if path != "" && key != "" { return Sequent(path, key, v) } if path != "" && i != nil { return Segment(path, *i, v) } return fmt.Errorf("see segSeqSubSeg for missing requirements") } func applyToBoolTFunc(path, key string, i *int, want bool) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got bool err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToFloat32TFunc(path, key string, i *int, want float32) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got float32 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToFloat64TFunc(path, key string, i *int, want float64) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got float64 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToIntTFunc(path, key string, i *int, want int) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got int err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToInt16TFunc(path, key string, i *int, want int16) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got int16 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToInt32TFunc(path, key string, i *int, want int32) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got int32 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToInt64TFunc(path, key string, i *int, want int64) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got int64 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToInt8TFunc(path, key string, i *int, want int8) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got int8 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToStringTFunc(path, key string, i *int, want string) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got string err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToUintTFunc(path, key string, i *int, want uint) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got uint err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToUint16TFunc(path, key string, i *int, want uint16) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got uint16 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToUint32TFunc(path, key string, i *int, want uint32) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got uint32 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToUint64TFunc(path, key string, i *int, want uint64) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got uint64 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToUint8TFunc(path, key string, i *int, want uint8) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got uint8 err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if got != want { t.Errorf(gwFmt, got, want) } } } func applyToUnmarshalerTFunc(path, key string, i *int, want []byte) func(*testing.T) { return func(t *testing.T) { subj := subject(path, key) if i != nil { subj = subject(path, key, *i) } var got custom err := segSeqSubSeg(path, key, i, &got) if unx(t, subj, err) { return } if !reflect.DeepEqual([]byte(got), want) { t.Errorf(gwFmt, got, want) } } } type custom []byte func (c *custom) UnmarshalSegment(d string) error { *c = []byte(d) return nil } func pti(i int) *int { return &i } var ( gwFmt = "got %v, want %v" gwxFmt = "subj '%v': got %v, want %v" ) type checkFunc func(*testing.T, interface{}, error) bool func unx(t *testing.T, subj interface{}, err error) bool { b := err != nil if b { t.Errorf(gwxFmt, subj, err, nil) } return b } func exp(t *testing.T, subj interface{}, err error) bool { b := err == nil if b { t.Errorf(gwxFmt, subj, nil, "{error}") } return b } func subject(path, key string, indexes ...int) string { s := "path " + path if key != "" { s += ", key " + key } if len(indexes) > 0 { s += ", indexes" } for _, i := range indexes { s += fmt.Sprintf(" %d", i) } return s } parth-2.0.1/segindex.go000066400000000000000000000033141342456527300150010ustar00rootroot00000000000000package parth func segStartIndexFromStart(path string, seg int) (int, bool) { if seg < 0 { return 0, false } for n, ct := 0, 0; n < len(path); n++ { if n > 0 && path[n] == '/' { ct++ } if ct == seg { return n, true } } return 0, false } func segStartIndexFromEnd(path string, seg int) (int, bool) { if seg > -1 { return 0, false } for n, ct := len(path)-1, 0; n >= 0; n-- { if path[n] == '/' || n == 0 { ct-- } if ct == seg { return n, true } } return 0, false } func segEndIndexFromStart(path string, seg int) (int, bool) { if seg < 1 { return 0, false } for n, ct := 0, 0; n < len(path); n++ { if path[n] == '/' && n > 0 { ct++ } if ct == seg { return n, true } if n+1 == len(path) && ct+1 == seg { return n + 1, true } } return 0, false } func segEndIndexFromEnd(path string, seg int) (int, bool) { if seg > 0 { return 0, false } if seg == 0 { return len(path), true } if len(path) == 1 && path[0] == '/' { return 0, true } for n, ct := len(path)-1, 0; n >= 0; n-- { if n == 0 || path[n] == '/' { ct-- } if ct == seg { return n, true } } return 0, false } func segIndexByKey(path, key string) (int, bool) { //nolint if path == "" || key == "" { return 0, false } for n := 0; n < len(path); n++ { si, ok := segStartIndexFromStart(path, n) if !ok { return 0, false } if len(path[si:]) == len(key)+1 { if path[si+1:] == key { return si, true } return 0, false } tmpEI, ok := segStartIndexFromStart(path[si:], 1) if !ok { return 0, false } if path[si+1:tmpEI+si] == key || n == 0 && path[0] != '/' && path[si:tmpEI+si] == key { return si, true } } return 0, false } parth-2.0.1/segindex_test.go000066400000000000000000000070121342456527300160370ustar00rootroot00000000000000package parth import "testing" func TestUnitSegStartIndexFromEnd(t *testing.T) { tests := []struct { i int s string want int okWant bool }{ {-1, "/test1", 0, true}, {-1, "/test1/test-2", 6, true}, {-1, "/test1/test-2/test_3", 13, true}, {-3, "test3/t3/", 0, true}, {-1, "test4/t4/", 8, true}, {-2, "/t5/f/fiv/55/5/fi/ve", 14, true}, {-1, "/", 0, true}, {-2, "/", 0, false}, {-4, "/test/out", 0, false}, {1, "/test/out", 0, false}, } for _, tt := range tests { got, okGot := segStartIndexFromEnd(tt.s, tt.i) if okGot != tt.okWant { t.Errorf(gwxFmt, tt.s, okGot, tt.okWant) continue } if got != tt.want { t.Errorf(gwxFmt, tt.s, got, tt.want) } } } func TestUnitSegStartIndexFromStart(t *testing.T) { tests := []struct { i int s string want int okWant bool }{ {0, "/test1", 0, true}, {1, "/test1/test-2", 6, true}, {2, "/t1-2/fd", 0, false}, {2, "/test1/test-2/test_3", 13, true}, {0, "test3/t3/", 0, true}, {1, "test4/t4/", 5, true}, {6, "/t5/f/fiv/55/5/fi/ve", 17, true}, {0, "/", 0, true}, {1, "/", 0, false}, {2, "/", 0, false}, {4, "/test/out", 0, false}, {-1, "/test/out", 0, false}, {2, "/0/1//", 4, true}, {3, "/0/1//", 5, true}, } for _, tt := range tests { got, okGot := segStartIndexFromStart(tt.s, tt.i) if okGot != tt.okWant { t.Errorf(gwxFmt, tt.s, okGot, tt.okWant) continue } if got != tt.want { t.Errorf(gwxFmt, tt.s, got, tt.want) } } } func TestUnitSegEndIndexFromEnd(t *testing.T) { tests := []struct { i int s string want int okWant bool }{ {0, "/test1", 6, true}, {-1, "/t1", 0, true}, {-1, "/test1/test-2", 6, true}, {-2, "/test1/t-2/t_3", 6, true}, {-3, "test3/t3/", 0, true}, {-1, "test4/t4/", 8, true}, {-2, "/t5/f/fiv/55/5/fi/ve", 14, true}, {-1, "/", 0, true}, {-4, "/test/out", 0, false}, {4, "/test/out", 0, false}, } for _, tt := range tests { got, okGot := segEndIndexFromEnd(tt.s, tt.i) if okGot != tt.okWant { t.Errorf(gwxFmt, tt.s, okGot, tt.okWant) continue } if got != tt.want { t.Errorf(gwxFmt, tt.s, got, tt.want) } } } func TestUnitSegEndIndexFromStart(t *testing.T) { tests := []struct { i int s string want int okWant bool }{ {1, "/test1", 6, true}, {2, "/test1/test-2", 13, true}, {2, "/test1/test-2/test_3", 13, true}, {1, "test3/t3/", 5, true}, {2, "test4/t4/", 8, true}, {5, "/t5/f/fiv/55/5/fi/ve", 14, true}, {1, "/", 1, true}, {-4, "/test/out", 0, false}, {4, "/test/out", 0, false}, } for _, tt := range tests { got, okGot := segEndIndexFromStart(tt.s, tt.i) if okGot != tt.okWant { t.Errorf(gwxFmt, tt.s, okGot, tt.okWant) continue } if got != tt.want { t.Errorf(gwxFmt, tt.s, got, tt.want) } } } func TestUnitSegIndexByKey(t *testing.T) { tests := []struct { k string s string want int okWant bool }{ {"test", "/1/test/3", 2, true}, {"test", "/0/test/1/test/3", 2, true}, {"2", "/2/t/3", 0, true}, {"3", "/1/test/3", 7, true}, {"4", "/44/44/33", 0, false}, {"best", "12/best/3", 2, true}, {"6", "6/tt/66", 0, true}, {"7", "1/test/7", 6, true}, {"first", "first/2/three", 0, true}, {"bad", "/ba/d/", 0, false}, {"11", "/4/56/11/", 5, true}, {"", "/4/56/11/", 0, false}, {"t", "", 0, false}, } for _, tt := range tests { got, okGot := segIndexByKey(tt.s, tt.k) if okGot != tt.okWant { t.Errorf(gwxFmt, tt.s, okGot, tt.okWant) continue } if got != tt.want { t.Errorf(gwxFmt, tt.s, got, tt.want) } } } parth-2.0.1/segtostr.go000066400000000000000000000110471342456527300150470ustar00rootroot00000000000000package parth import ( "strconv" "unicode" ) func segmentToBool(path string, i int) (bool, error) { s, err := segmentToString(path, i) if err != nil { return false, err } v, err := strconv.ParseBool(s) if err != nil { return false, ErrDataUnparsable } return v, nil } func segmentToFloatN(path string, i, size int) (float64, error) { ss, err := segmentToString(path, i) if err != nil { return 0.0, err } s, ok := firstFloatFromString(ss) if !ok { return 0.0, err } v, err := strconv.ParseFloat(s, size) if err != nil { return 0.0, ErrDataUnparsable } return v, nil } func segmentToIntN(path string, i, size int) (int64, error) { ss, err := segmentToString(path, i) if err != nil { return 0, err } s, ok := firstIntFromString(ss) if !ok { return 0, ErrDataUnparsable } v, err := strconv.ParseInt(s, 10, size) if err != nil { return 0, ErrDataUnparsable } return v, nil } func segmentToString(path string, i int) (string, error) { j := i + 1 if i < 0 { i-- } s, err := Span(path, i, j) if err != nil { return "", err } if s[0] == '/' { s = s[1:] } return s, nil } func segmentToUintN(path string, i, size int) (uint64, error) { ss, err := segmentToString(path, i) if err != nil { return 0, err } s, ok := firstUintFromString(ss) if !ok { return 0, ErrDataUnparsable } v, err := strconv.ParseUint(s, 10, size) if err != nil { return 0, ErrDataUnparsable } return v, nil } func subSegToBool(path, key string, i int) (bool, error) { s, err := subSegToString(path, key, i) if err != nil { return false, err } v, err := strconv.ParseBool(s) if err != nil { return false, ErrDataUnparsable } return v, nil } func subSegToFloatN(path, key string, i, size int) (float64, error) { ss, err := subSegToString(path, key, i) if err != nil { return 0.0, err } s, ok := firstFloatFromString(ss) if !ok { return 0.0, ErrDataUnparsable } v, err := strconv.ParseFloat(s, size) if err != nil { return 0.0, ErrDataUnparsable } return v, nil } func subSegToIntN(path, key string, i, size int) (int64, error) { ss, err := subSegToString(path, key, i) if err != nil { return 0, err } s, ok := firstIntFromString(ss) if !ok { return 0, ErrDataUnparsable } v, err := strconv.ParseInt(s, 10, size) if err != nil { return 0, ErrDataUnparsable } return v, nil } func subSegToString(path, key string, i int) (string, error) { ki, ok := segIndexByKey(path, key) if !ok { return "", ErrKeySegNotFound } i++ s, err := segmentToString(path[ki:], i) if err != nil { return "", err } return s, nil } func subSegToUintN(path, key string, i, size int) (uint64, error) { ss, err := subSegToString(path, key, i) if err != nil { return 0, err } s, ok := firstUintFromString(ss) if !ok { return 0, ErrDataUnparsable } v, err := strconv.ParseUint(s, 10, size) if err != nil { return 0, ErrDataUnparsable } return v, nil } func firstUintFromString(s string) (string, bool) { ind, l := 0, 0 for n := 0; n < len(s); n++ { if unicode.IsDigit(rune(s[n])) { if l == 0 { ind = n } l++ } else { if l == 0 && s[n] == '.' { if n+1 < len(s) && unicode.IsDigit(rune(s[n+1])) { return "0", true } break } if l > 0 { break } } } if l == 0 { return "", false } return s[ind : ind+l], true } func firstIntFromString(s string) (string, bool) { //nolint ind, l := 0, 0 for n := 0; n < len(s); n++ { if unicode.IsDigit(rune(s[n])) { if l == 0 { ind = n } l++ } else if s[n] == '-' { if l == 0 { ind = n l++ } else { break } } else { if l == 0 && s[n] == '.' { if n+1 < len(s) && unicode.IsDigit(rune(s[n+1])) { return "0", true } break } if l > 0 { break } } } if l == 0 { return "", false } return s[ind : ind+l], true } func firstFloatFromString(s string) (string, bool) { //nolint c, ind, l := 0, 0, 0 for n := 0; n < len(s); n++ { if unicode.IsDigit(rune(s[n])) { if l == 0 { ind = n } l++ } else if s[n] == '-' { if l == 0 { ind = n l++ } else { break } } else if s[n] == '.' { if l == 0 { ind = n } if c > 0 { break } l++ c++ } else if s[n] == 'e' && l > 0 && n+1 < len(s) && s[n+1] == '+' { l++ } else if s[n] == '+' && l > 0 && s[n-1] == 'e' { if n+1 < len(s) && unicode.IsDigit(rune(s[n+1])) { l++ continue } l-- break } else { if l > 0 { break } } } if l == 0 || s[ind:ind+l] == "." { return "", false } return s[ind : ind+l], true } parth-2.0.1/segtostr_test.go000066400000000000000000000044731342456527300161130ustar00rootroot00000000000000package parth import "testing" func TestUnitFirstFloatFromString(t *testing.T) { tests := []struct { s string want string okWant bool }{ {"/0.1", "0.1", true}, {"/0.2a", "0.2", true}, {"/aaaa1.3", "1.3", true}, {"/4", "4", true}, {"/5aaaa", "5", true}, {"/aaa6aa", "6", true}, {"/.7.aaaa", ".7", true}, {"/.8aa", ".8", true}, {"/-9", "-9", true}, {"/10-", "10", true}, {"/3.14e+11", "3.14e+11", true}, {"/3.14e.+12", "3.14", true}, {"/3.14e+.13", "3.14", true}, {"/3.14e+.13", "3.14", true}, {"/error", "", false}, {"/.", "", false}, } for _, tt := range tests { got, okGot := firstFloatFromString(tt.s) if okGot != tt.okWant { t.Errorf(gwxFmt, tt.s, okGot, tt.okWant) continue } if got != tt.want { t.Errorf(gwxFmt, tt.s, got, tt.want) } } } func TestUnitFirstIntFromString(t *testing.T) { var tests = []struct { s string want string okWant bool }{ {"0.1", "0", true}, {"0.2a", "0", true}, {"aaaa1.3", "1", true}, {"4", "4", true}, {"5aaaa", "5", true}, {"aaa6aa", "6", true}, {".7.aaaa", "0", true}, {".8aa", "0", true}, {"-9", "-9", true}, {"10-", "10", true}, {"3.14e+11", "3", true}, {"3.14e.+12", "3", true}, {"3.14e+.13", "3", true}, {"18446744073709551615", "18446744073709551615", true}, {".", "", false}, {"error", "", false}, } for _, tt := range tests { got, okGot := firstIntFromString(tt.s) if okGot != tt.okWant { t.Errorf(gwxFmt, tt.s, okGot, tt.okWant) continue } if got != tt.want { t.Errorf(gwxFmt, tt.s, got, tt.want) } } } func TestUnitFirstUintFromString(t *testing.T) { var tests = []struct { s string want string okWant bool }{ {"0.1", "0", true}, {"0.2a", "0", true}, {"aaaa1.3", "1", true}, {"4", "4", true}, {"5aaaa", "5", true}, {"aaa6aa", "6", true}, {".7.aaaa", "0", true}, {".8aa", "0", true}, {"-9", "9", true}, {"10-", "10", true}, {"3.14e+11", "3", true}, {"3.14e.+12", "3", true}, {"3.14e+.13", "3", true}, {"18446744073709551615", "18446744073709551615", true}, {".", "", false}, {"error", "", false}, } for _, tt := range tests { got, okGot := firstUintFromString(tt.s) if okGot != tt.okWant { t.Errorf(gwxFmt, tt.s, okGot, tt.okWant) continue } if got != tt.want { t.Errorf(gwxFmt, tt.s, got, tt.want) } } }