pax_global_header 0000666 0000000 0000000 00000000064 13533272317 0014520 g ustar 00root root 0000000 0000000 52 comment=0275d16782562f77a842ef80583eea89a715f613
tmc-0.5.1/ 0000775 0000000 0000000 00000000000 13533272317 0012306 5 ustar 00root root 0000000 0000000 tmc-0.5.1/.github/ 0000775 0000000 0000000 00000000000 13533272317 0013646 5 ustar 00root root 0000000 0000000 tmc-0.5.1/.github/workflows/ 0000775 0000000 0000000 00000000000 13533272317 0015703 5 ustar 00root root 0000000 0000000 tmc-0.5.1/.github/workflows/test.yml 0000664 0000000 0000000 00000001240 13533272317 0017402 0 ustar 00root root 0000000 0000000 name: Test
on: [push, pull_request]
jobs:
build:
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.12
id: go
- name: Checkout
uses: actions/checkout@v1
- name: Test
run: go test . -coverprofile=coverage.txt -covermode=atomic
- name: Test race
run: go test -race .
- name: Upload to Codecov
if: matrix.os == 'ubuntu-latest'
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: bash <(curl -s https://codecov.io/bash)
tmc-0.5.1/.gitignore 0000664 0000000 0000000 00000000315 13533272317 0014275 0 ustar 00root root 0000000 0000000 # Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
coverage.txt
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
tmc-0.5.1/LICENSE 0000664 0000000 0000000 00000002065 13533272317 0013316 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2019 Bjørn Erik Pedersen
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.
tmc-0.5.1/README.md 0000664 0000000 0000000 00000012006 13533272317 0013564 0 ustar 00root root 0000000 0000000
Codec for a Typed Map
Provides round-trip serialization of typed Go maps.

### How to Use
See the [GoDoc](https://godoc.org/github.com/bep/tmc) for some basic examples and how to configure custom codec, adapters etc.
### Why?
Text based serialization formats like JSON and YAML are convenient, but when used with Go maps, most type information gets lost in translation.
Listed below is a round-trip example in JSON (see https://play.golang.org/p/zxt-wi4Ljz3 for a runnable version):
```go
package main
import (
"encoding/json"
"log"
"math/big"
"time"
"github.com/kr/pretty"
)
func main() {
mi := map[string]interface{}{
"vstring": "Hello",
"vint": 32,
"vrat": big.NewRat(1, 2),
"vtime": time.Now(),
"vduration": 3 * time.Second,
"vsliceint": []int{1, 3, 4},
"nested": map[string]interface{}{
"vint": 55,
"vduration": 5 * time.Second,
},
"nested-typed-int": map[string]int{
"vint": 42,
},
"nested-typed-duration": map[string]time.Duration{
"v1": 5 * time.Second,
"v2": 10 * time.Second,
},
}
data, err := json.Marshal(mi)
if err != nil {
log.Fatal(err)
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
log.Fatal(err)
}
pretty.Print(m)
}
```
This prints:
```go
map[string]interface {}{
"vint": float64(32),
"vrat": "1/2",
"vtime": "2009-11-10T23:00:00Z",
"vduration": float64(3e+09),
"vsliceint": []interface {}{
float64(1),
float64(3),
float64(4),
},
"vstring": "Hello",
"nested": map[string]interface {}{
"vduration": float64(5e+09),
"vint": float64(55),
},
"nested-typed-duration": map[string]interface {}{
"v2": float64(1e+10),
"v1": float64(5e+09),
},
"nested-typed-int": map[string]interface {}{
"vint": float64(42),
},
}
```
And that is very different from the origin:
* All numbers are now `float64`
* `time.Duration` is also `float64`
* `time.Now` and `*big.Rat` are strings
* Slices are `[]interface {}`, maps `map[string]interface {}`
So, for structs, you can work around some of the limitations above with custom `MarshalJSON`, `UnmarshalJSON`, `MarshalText` and `UnmarshalText`.
For the commonly used flexible and schema-less`map[string]interface {}` this is, as I'm aware of, not an option.
Using this library, the above can be written to (see https://play.golang.org/p/PlDetQP5aWd for a runnable example):
```go
package main
import (
"log"
"math/big"
"time"
"github.com/bep/tmc"
"github.com/kr/pretty"
)
func main() {
mi := map[string]interface{}{
"vstring": "Hello",
"vint": 32,
"vrat": big.NewRat(1, 2),
"vtime": time.Now(),
"vduration": 3 * time.Second,
"vsliceint": []int{1, 3, 4},
"nested": map[string]interface{}{
"vint": 55,
"vduration": 5 * time.Second,
},
"nested-typed-int": map[string]int{
"vint": 42,
},
"nested-typed-duration": map[string]time.Duration{
"v1": 5 * time.Second,
"v2": 10 * time.Second,
},
}
c, err := tmc.New()
if err != nil {
log.Fatal(err)
}
data, err := c.Marshal(mi)
if err != nil {
log.Fatal(err)
}
m := make(map[string]interface{})
if err := c.Unmarshal(data, &m); err != nil {
log.Fatal(err)
}
pretty.Print(m)
}
```
This prints:
```go
map[string]interface {}{
"vduration": time.Duration(3000000000),
"vint": int(32),
"nested-typed-int": map[string]int{"vint":42},
"vsliceint": []int{1, 3, 4},
"vstring": "Hello",
"vtime": time.Time{
wall: 0x0,
ext: 63393490800,
loc: (*time.Location)(nil),
},
"nested": map[string]interface {}{
"vduration": time.Duration(5000000000),
"vint": int(55),
},
"nested-typed-duration": map[string]time.Duration{"v1":5000000000, "v2":10000000000},
"vrat": &big.Rat{
a: big.Int{
neg: false,
abs: {0x1},
},
b: big.Int{
neg: false,
abs: {0x2},
},
},
}
```
### Performance
The implementation is easy to reason aobut (it uses reflection), but It's not particulary fast and probably not suited for _big data_. A simple benchmark with a roundtrip marshal/unmarshal is included. On my MacBook it shows:
```bash
BenchmarkCodec/JSON_regular-4 50000 27523 ns/op 6742 B/op 171 allocs/op
BenchmarkCodec/JSON_typed-4 20000 66644 ns/op 16234 B/op 411 allocs/op
```
tmc-0.5.1/adapters.go 0000664 0000000 0000000 00000006654 13533272317 0014453 0 ustar 00root root 0000000 0000000 // Copyright © 2019 Bjørn Erik Pedersen .
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package tmc
import (
"encoding"
"fmt"
"math/big"
"reflect"
"strconv"
"time"
)
// Adapter wraps a type to preserve type information when encoding and decoding
// a map.
//
// The simples way to create new adapters is via the NewAdapter function.
type Adapter interface {
FromString(s string) (interface{}, error)
MarshalText() (text []byte, err error)
Type() reflect.Type
Wrap(v interface{}) Adapter
}
var (
// DefaultTypeAdapters contains the default set of type adapters.
DefaultTypeAdapters = []Adapter{
// Time
NewAdapter(time.Now(), nil, nil),
NewAdapter(
3*time.Hour,
func(s string) (interface{}, error) { return time.ParseDuration(s) },
func(v interface{}) (string, error) { return v.(time.Duration).String(), nil },
),
// Numbers
NewAdapter(big.NewRat(1, 2), nil, nil),
NewAdapter(
int(32),
func(s string) (interface{}, error) {
return strconv.Atoi(s)
},
func(v interface{}) (string, error) {
return strconv.Itoa(v.(int)), nil
},
),
}
)
// NewAdapter creates a new adapter that wraps the target type.
//
// fromString can be omitted if target implements encoding.TextUnmarshaler.
// toString can be omitted if target implements encoding.TextMarshaler.
//
// It will panic if it can not be created.
func NewAdapter(
target interface{},
fromString func(s string) (interface{}, error),
toString func(v interface{}) (string, error)) Adapter {
targetValue := reflect.ValueOf(target)
targetType := targetValue.Type()
wasPointer := targetType.Kind() == reflect.Ptr
if !wasPointer {
// Need the pointer to see the TextUnmarshaler implementation.
v := targetValue
targetValue = reflect.New(targetType)
targetValue.Elem().Set(v)
}
if fromString == nil {
if _, ok := targetValue.Interface().(encoding.TextUnmarshaler); ok {
fromString = func(s string) (interface{}, error) {
typ := targetType
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
v := reflect.New(typ)
err := v.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(s))
if err != nil {
return nil, err
}
if !wasPointer {
v = v.Elem()
}
return v.Interface(), nil
}
} else {
panic(fmt.Sprintf("%T can not be unmarshaled", target))
}
}
var marshalText func(v interface{}) ([]byte, error)
if toString != nil {
marshalText = func(v interface{}) ([]byte, error) {
s, err := toString(v)
return []byte(s), err
}
} else if _, ok := target.(encoding.TextMarshaler); ok {
marshalText = func(v interface{}) ([]byte, error) {
return v.(encoding.TextMarshaler).MarshalText()
}
} else {
panic(fmt.Sprintf("%T can not be marshaled", target))
}
return &adapter{
targetType: targetType,
fromString: fromString,
marshalText: marshalText,
}
}
var _ Adapter = (*adapter)(nil)
type adapter struct {
fromString func(s string) (interface{}, error)
marshalText func(v interface{}) (text []byte, err error)
targetType reflect.Type
target interface{}
}
func (a *adapter) FromString(s string) (interface{}, error) {
return a.fromString(s)
}
func (a *adapter) MarshalText() (text []byte, err error) {
return a.marshalText(a.target)
}
func (a adapter) Type() reflect.Type {
return a.targetType
}
func (a adapter) Wrap(v interface{}) Adapter {
a.target = v
return &a
}
tmc-0.5.1/codec.go 0000664 0000000 0000000 00000017650 13533272317 0013723 0 ustar 00root root 0000000 0000000 // Copyright © 2019 Bjørn Erik Pedersen .
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package tmc
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
)
// JSONMarshaler encodes and decodes JSON and is the default used in this
// codec.
var JSONMarshaler = new(jsonMarshaler)
// New creates a new Coded with some optional options.
func New(opts ...Option) (*Codec, error) {
c := &Codec{
typeSep: "|",
marshaler: JSONMarshaler,
typeAdapters: DefaultTypeAdapters,
typeAdaptersMap: make(map[reflect.Type]Adapter),
typeAdaptersStringMap: make(map[string]Adapter),
}
for _, opt := range opts {
if err := opt(c); err != nil {
return c, err
}
}
for _, w := range c.typeAdapters {
tp := w.Type()
c.typeAdaptersMap[tp] = w
c.typeAdaptersStringMap[tp.String()] = w
}
return c, nil
}
// Option configures the Codec.
type Option func(c *Codec) error
// WithTypeSep sets the separator to use before the type information encoded in
// the key field. Default is "|".
func WithTypeSep(sep string) func(c *Codec) error {
return func(c *Codec) error {
if sep == "" {
return errors.New("separator cannot be empty")
}
c.typeSep = sep
return nil
}
}
// WithMarshalUnmarshaler sets the MarshalUnmarshaler to use.
// Default is JSONMarshaler.
func WithMarshalUnmarshaler(marshaler MarshalUnmarshaler) func(c *Codec) error {
return func(c *Codec) error {
c.marshaler = marshaler
return nil
}
}
// WithTypeAdapters sets the type adapters to use. Note that if more than one
// adapter exists for the same type, the last one will win. This means that
// if you want to use the default adapters, but override some of them, you
// can do:
//
// adapters := append(typedmapcodec.DefaultTypeAdapters, mycustomAdapters ...)
// codec := typedmapcodec.New(WithTypeAdapters(adapters))
//
func WithTypeAdapters(typeAdapters []Adapter) func(c *Codec) error {
return func(c *Codec) error {
c.typeAdapters = typeAdapters
return nil
}
}
// Codec provides methods to marshal and unmarshal a Go map while preserving
// type information.
type Codec struct {
typeSep string
marshaler MarshalUnmarshaler
typeAdapters []Adapter
typeAdaptersMap map[reflect.Type]Adapter
typeAdaptersStringMap map[string]Adapter
}
// Marshal accepts a Go map and marshals it to the configured marshaler
// anntated with type information.
func (c *Codec) Marshal(v interface{}) ([]byte, error) {
m, err := c.toTypedMap(v)
if err != nil {
return nil, err
}
return c.marshaler.Marshal(m)
}
// Unmarshal unmarshals the given data to the given Go map, using
// any annotated type information found to preserve the type information
// stored in Marshal.
func (c *Codec) Unmarshal(data []byte, v interface{}) error {
if err := c.marshaler.Unmarshal(data, v); err != nil {
return err
}
_, err := c.fromTypedMap(v)
return err
}
func (c *Codec) newKey(key reflect.Value, a Adapter) reflect.Value {
return reflect.ValueOf(fmt.Sprintf("%s%s%s", key, c.typeSep, a.Type()))
}
func (c *Codec) fromTypedMap(mi interface{}) (reflect.Value, error) {
m := reflect.ValueOf(mi)
if m.Kind() == reflect.Ptr {
m = m.Elem()
}
if m.Kind() != reflect.Map {
return reflect.Value{}, errors.New("must be a Map")
}
keyKind := m.Type().Key().Kind()
if keyKind == reflect.Interface {
// We only support string keys.
// YAML creates map[interface {}]interface {}, so try to convert it.
var err error
m, err = c.toStringMap(m)
if err != nil {
return reflect.Value{}, err
}
}
for _, key := range m.MapKeys() {
v := indirectInterface(m.MapIndex(key))
var (
keyStr = key.String()
keyPlain string
keyType string
)
sepIdx := strings.LastIndex(keyStr, c.typeSep)
if sepIdx != -1 {
keyPlain = keyStr[:sepIdx]
keyType = keyStr[sepIdx+len(c.typeSep):]
}
adapter, found := c.typeAdaptersStringMap[keyType]
if !found {
if v.Kind() == reflect.Map {
var err error
v, err = c.fromTypedMap(v.Interface())
if err != nil {
return reflect.Value{}, err
}
m.SetMapIndex(key, v)
}
continue
}
switch v.Kind() {
case reflect.Map:
mm := reflect.MakeMap(reflect.MapOf(stringType, adapter.Type()))
for _, key := range v.MapKeys() {
vv := indirectInterface(v.MapIndex(key))
nv, err := adapter.FromString(vv.String())
if err != nil {
return reflect.Value{}, err
}
mm.SetMapIndex(indirectInterface(key), reflect.ValueOf(nv))
}
m.SetMapIndex(reflect.ValueOf(keyPlain), mm)
case reflect.Slice:
slice := reflect.MakeSlice(reflect.SliceOf(adapter.Type()), v.Len(), v.Cap())
for i := 0; i < v.Len(); i++ {
vv := indirectInterface(v.Index(i))
nv, err := adapter.FromString(vv.String())
if err != nil {
return reflect.Value{}, err
}
slice.Index(i).Set(reflect.ValueOf(nv))
}
m.SetMapIndex(reflect.ValueOf(keyPlain), slice)
default:
nv, err := adapter.FromString(v.String())
if err != nil {
return reflect.Value{}, err
}
m.SetMapIndex(reflect.ValueOf(keyPlain), reflect.ValueOf(nv))
}
m.SetMapIndex(key, reflect.Value{})
}
return m, nil
}
var (
interfaceMapType = reflect.TypeOf(make(map[string]interface{}))
interfaceSliceType = reflect.TypeOf([]interface{}{})
stringType = reflect.TypeOf("")
)
func (c *Codec) toTypedMap(mi interface{}) (interface{}, error) {
mv := reflect.ValueOf(mi)
if mv.Kind() != reflect.Map || mv.Type().Key().Kind() != reflect.String {
return nil, errors.New("must provide a map with string keys")
}
m := reflect.MakeMap(interfaceMapType)
for _, key := range mv.MapKeys() {
v := indirectInterface(mv.MapIndex(key))
switch v.Kind() {
case reflect.Map:
if wrapper, found := c.typeAdaptersMap[v.Type().Elem()]; found {
mm := reflect.MakeMap(interfaceMapType)
for _, key := range v.MapKeys() {
mm.SetMapIndex(key, reflect.ValueOf(wrapper.Wrap(v.MapIndex(key).Interface())))
}
m.SetMapIndex(c.newKey(key, wrapper), mm)
} else {
nested, err := c.toTypedMap(v.Interface())
if err != nil {
return nil, err
}
m.SetMapIndex(key, reflect.ValueOf(nested))
}
continue
case reflect.Slice:
if adapter, found := c.typeAdaptersMap[v.Type().Elem()]; found {
slice := reflect.MakeSlice(interfaceSliceType, v.Len(), v.Cap())
for i := 0; i < v.Len(); i++ {
slice.Index(i).Set(reflect.ValueOf(adapter.Wrap(v.Index(i).Interface())))
}
m.SetMapIndex(c.newKey(key, adapter), slice)
continue
}
}
if adapter, found := c.typeAdaptersMap[v.Type()]; found {
m.SetMapIndex(c.newKey(key, adapter), reflect.ValueOf(adapter.Wrap(v.Interface())))
} else {
m.SetMapIndex(key, v)
}
}
return m.Interface(), nil
}
func (c *Codec) toStringMap(mi reflect.Value) (reflect.Value, error) {
elemType := mi.Type().Elem()
m := reflect.MakeMap(reflect.MapOf(stringType, elemType))
for _, key := range mi.MapKeys() {
key = indirectInterface(key)
if key.Kind() != reflect.String {
return reflect.Value{}, errors.New("this library supports only string keys in maps")
}
vv := mi.MapIndex(key)
m.SetMapIndex(reflect.ValueOf(key.String()), vv)
}
return m, nil
}
// MarshalUnmarshaler is the interface that must be implemented if you want to
// add support for more than JSON to this codec.
type MarshalUnmarshaler interface {
Marshal(v interface{}) ([]byte, error)
Unmarshal(b []byte, v interface{}) error
}
type jsonMarshaler int
func (jsonMarshaler) Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
func (jsonMarshaler) Unmarshal(b []byte, v interface{}) error {
return json.Unmarshal(b, v)
}
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {
return v
}
if v.IsNil() {
return reflect.Value{}
}
return v.Elem()
}
tmc-0.5.1/codec_test.go 0000664 0000000 0000000 00000010253 13533272317 0014752 0 ustar 00root root 0000000 0000000 // Copyright © 2019 Bjørn Erik Pedersen .
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package tmc
import (
"encoding/json"
"math/big"
"testing"
"time"
yaml "gopkg.in/yaml.v2"
qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp"
)
func TestRoundtrip(t *testing.T) {
c := qt.New(t)
src := newTestMap()
for _, test := range []struct {
name string
options []Option
dataAssert func(c *qt.C, data string)
}{
{"Default", nil,
func(c *qt.C, data string) { c.Assert(data, qt.Contains, `{"nested":{"vduration|time.Duration":"5s"`) }},
{"Custom type separator",
[]Option{
WithTypeSep("TYPE:"),
},
func(c *qt.C, data string) { c.Assert(data, qt.Contains, "TYPE:") },
},
{"YAML",
[]Option{
WithMarshalUnmarshaler(new(yamlMarshaler)),
},
func(c *qt.C, data string) { c.Assert(data, qt.Contains, "vduration|time.Duration: 3s") },
},
{"JSON indent",
[]Option{
WithMarshalUnmarshaler(new(jsonMarshalerIndent)),
},
func(c *qt.C, data string) {
},
},
} {
test := test
c.Run(test.name, func(c *qt.C) {
c.Parallel()
codec, err := New(test.options...)
c.Assert(err, qt.IsNil)
data, err := codec.Marshal(src)
c.Assert(err, qt.IsNil)
//c.Log(string(data))
if test.dataAssert != nil {
test.dataAssert(c, string(data))
}
dst := make(map[string]interface{})
c.Assert(codec.Unmarshal(data, &dst), qt.IsNil)
c.Assert(dst, eq, src)
})
}
}
func TestErrors(t *testing.T) {
c := qt.New(t)
codec, err := New()
c.Assert(err, qt.IsNil)
marshal := func(v interface{}) error {
_, err := codec.Marshal(v)
return err
}
// OK
c.Assert(marshal(map[string]interface{}{"32": "a"}), qt.IsNil)
c.Assert(marshal(map[string]int{"32": 32}), qt.IsNil)
// Should fail
c.Assert(marshal([]string{"a"}), qt.Not(qt.IsNil))
c.Assert(marshal(map[int]interface{}{32: "a"}), qt.Not(qt.IsNil))
c.Assert(marshal(map[string]interface{}{"a": map[int]string{32: "32"}}), qt.Not(qt.IsNil))
}
func BenchmarkCodec(b *testing.B) {
b.Run("JSON regular", func(b *testing.B) {
b.StopTimer()
mi := newTestMap()
b.StartTimer()
for i := 0; i < b.N; i++ {
data, err := json.Marshal(mi)
if err != nil {
b.Fatal(err)
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
b.Fatal(err)
}
}
})
b.Run("JSON typed", func(b *testing.B) {
b.StopTimer()
mi := newTestMap()
c, err := New()
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
data, err := c.Marshal(mi)
if err != nil {
b.Fatal(err)
}
m := make(map[string]interface{})
if err := c.Unmarshal(data, &m); err != nil {
b.Fatal(err)
}
}
})
}
func newTestMap() map[string]interface{} {
return map[string]interface{}{
"vstring": "Hello1",
"vstring|": "Hello2",
"vstring|foo": "Hello3",
// Numbers
"vint": 32,
"vfloat64": float64(3.14159),
"vrat": big.NewRat(1, 2),
// Time
"vtime": time.Now(),
"vduration": 3 * time.Second,
"vsliceint": []int{1, 3, 4},
"nested": map[string]interface{}{
"vint": 55,
"vduration": 5 * time.Second,
},
"nested-typed-int": map[string]int{
"vint": 42,
},
"nested-typed-duration": map[string]time.Duration{
"v1": 5 * time.Second,
"v2": 10 * time.Second,
},
}
}
var eq = qt.CmpEquals(
cmp.Comparer(
func(v1, v2 *big.Rat) bool {
return v1.RatString() == v2.RatString()
},
),
cmp.Comparer(func(v1, v2 time.Time) bool {
// UnmarshalText always create times with no monotonic clock reading,
// so we cannot compare with ==.
// TODO(bep) improve this
return v1.Unix() == v2.Unix()
}),
)
type yamlMarshaler int
func (yamlMarshaler) Marshal(v interface{}) ([]byte, error) {
return yaml.Marshal(v)
}
func (yamlMarshaler) Unmarshal(b []byte, v interface{}) error {
return yaml.Unmarshal(b, v)
}
// Useful for debugging
type jsonMarshalerIndent int
func (jsonMarshalerIndent) Marshal(v interface{}) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}
func (jsonMarshalerIndent) Unmarshal(b []byte, v interface{}) error {
return json.Unmarshal(b, v)
}
tmc-0.5.1/codecov.yml 0000664 0000000 0000000 00000000216 13533272317 0014452 0 ustar 00root root 0000000 0000000 coverage:
status:
project:
default:
target: auto
threshold: 0.5%
patch: off
comment:
require_changes: true
tmc-0.5.1/examples_test.go 0000664 0000000 0000000 00000002376 13533272317 0015522 0 ustar 00root root 0000000 0000000 // Copyright © 2019 Bjørn Erik Pedersen .
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package tmc_test
import (
"fmt"
"log"
"github.com/bep/tmc"
yaml "gopkg.in/yaml.v2"
)
func Example() {
m1 := map[string]interface{}{"num": 42}
c, err := tmc.New()
if err != nil {
log.Fatal(err)
}
data, err := c.Marshal(m1)
if err != nil {
log.Fatal(err)
}
m2 := make(map[string]interface{})
err = c.Unmarshal(data, &m2)
if err != nil {
log.Fatal(err)
}
num := m2["num"]
fmt.Printf("%v (%T)", num, num)
// Output: 42 (int)
}
func ExampleWithMarshalUnmarshaler() {
m1 := map[string]interface{}{"num": 42}
c, err := tmc.New(tmc.WithMarshalUnmarshaler(new(yamlMarshaler)))
if err != nil {
log.Fatal(err)
}
data, err := c.Marshal(m1)
if err != nil {
log.Fatal(err)
}
m2 := make(map[string]interface{})
err = c.Unmarshal(data, &m2)
if err != nil {
log.Fatal(err)
}
num := m2["num"]
fmt.Printf("%v (%T)", num, num)
// Output: 42 (int)
}
type yamlMarshaler int
func (yamlMarshaler) Marshal(v interface{}) ([]byte, error) {
return yaml.Marshal(v)
}
func (yamlMarshaler) Unmarshal(b []byte, v interface{}) error {
return yaml.Unmarshal(b, v)
}
tmc-0.5.1/go.mod 0000664 0000000 0000000 00000000260 13533272317 0013412 0 ustar 00root root 0000000 0000000 module github.com/bep/tmc
go 1.12
require (
github.com/bep/debounce v1.2.0
github.com/frankban/quicktest v1.4.1
github.com/google/go-cmp v0.3.0
gopkg.in/yaml.v2 v2.2.2
)
tmc-0.5.1/go.sum 0000664 0000000 0000000 00000002357 13533272317 0013450 0 ustar 00root root 0000000 0000000 github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg=
github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=