human

package module
v1.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2018 License: MIT Imports: 11 Imported by: 0

README

go-human

license GoDoc Build Status codecov Go Report Card

go-human is a Go library for struct to human-readable text encoding. This library is maintained by the Anexia R&D team.

Motivation

The primary motivation behind creating go-human as the need for a human-readable text encoder for arbitrary Go objects which can be used as a drop-in replacement for Go's encoding/json encoder.

The story behind how go-human came to be

The idea arose during development of an internal project, which provides a CLI binary for management purposes of a larger overall system. Initially the CLI binary was intended as a tool for human interaction with said system, but it soon became clear that some operations would need to be automated. Due to this need the tool would need to be able to output JSON, as well as a human-readable representation of the information retrieved via the API it communicated with.

In order to minimize the code required to fulfill both of these requirements we created go-human and are now using a commandline flag which configures the output mode. Behind the scenes both the JSON and go-human encoders implement the same interface, which in turn allowed us to have a single switch in the application which affects all output generated by it.

Format

The text format go-human generates is loosely based on YAML, but with the distinction that this format was never intended to be parsed by a machine.

Please note that the exact format is intentionally not defined formally and parsing of text generated by this library is strongly discouraged.

Examples

Examples can be found in the package documentation at godoc.org.

The corresponding code can be found in go-human's GitHub repository inside the example_test.go file.

Install

With a correctly configured Go toolchain:

go get -u github.com/anexia-it/go-human

Issue tracker

Issues in go-human are tracked using the corresponding GitHub project's issue tracker.

Status

The current release is v1.0.0.

Changes to go-human are subject to semantic versioning.

The ChangeLog provides information on releases and changes.

License

go-human is licensed under the terms of the MIT license.

Documentation

Overview

Package human provides an encoding from go structs to human readable text

Index

Examples

Constants

View Source
const DefaultIndent = 2

DefaultIndent defines the default indentation

View Source
const DefaultListSymbol = "*"

DefaultListSymbol defines the default list symbol

View Source
const DefaultTagName = "human"

DefaultTagName defines the default tag name

Variables

View Source
var ErrInvalidTagName = errors.New("invalid tag name")

ErrInvalidTagName indicates that no tag name was specified.

View Source
var ErrListSymbolsEmpty = errors.New("no list symbols provided")

ErrListSymbolsEmpty indicates that no list symbols were provided.

Functions

func IsNilOrEmpty

func IsNilOrEmpty(i interface{}, v reflect.Value) bool

IsNilOrEmpty checks if a passed interface is either nil or of the type's zero value.

The zero value depends on the passed type. For example, the zero value of a string is an empty string.

func ParseTag

func ParseTag(tag string) (name string, omitEmpty bool, err error)

ParseTag parses a tag string and returns the corresponding name, omitEmpty flag and a possible error

Types

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder writes human readable text to an output stream.

func NewEncoder

func NewEncoder(w io.Writer, opts ...Option) (encoder *Encoder, err error)

NewEncoder returns a new encoder that writes to w.

func (*Encoder) Encode

func (e *Encoder) Encode(v interface{}) error

Encode writes the human encoding of v to the stream.

Example (AnonymousFiled)

Encode test with anonymous field

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// AnonymousFieldTest test struct
type AnonymousFieldTest struct {
	int
	Text string
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}

	//anonymous int field is ignored
	testStruct := AnonymousFieldTest{
		Text: "test",
	}

	if err := enc.Encode(testStruct); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

Text: test
Example (IteratingSlice)

Encode test iterating slice of structs

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// SimpleChild test struct
type SimpleChild struct {
	Name      string
	Property1 uint64  `human:"-"`
	Property2 float64 `human:",omitempty"`
}

// SliceTest test struct
type SliceTest struct {
	IntSlice    []int
	StructSlice []SimpleChild
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}

	child1 := SimpleChild{
		Name:      "Person1",
		Property2: 4.5,
		Property1: 0, // should be ignored
	}

	child2 := SimpleChild{
		Name:      "Person2",
		Property2: 4.5,
		Property1: 0, // should be ignored
	}

	structSlice := []SimpleChild{child1, child2}
	testStruct := SliceTest{
		StructSlice: structSlice,
	}

	for _, s := range testStruct.StructSlice {
		if err := enc.Encode(s); err != nil {
			fmt.Printf("ERROR: %s\n", err.Error())
			return
		}
	}
}
Output:

Name: Person1
Property2: 4.5

Name: Person2
Property2: 4.5
Example (Simple)

Encode test with simple test struct to test encode with ignored fields

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// SimpleChild test struct
type SimpleChild struct {
	Name      string
	Property1 uint64  `human:"-"`
	Property2 float64 `human:",omitempty"`
}

// SimpleTest test struct
type SimpleTest struct {
	Var1  string
	Var2  int `human:"variable_2"`
	Child SimpleChild
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}
	testStruct := SimpleTest{
		Var1: "v1",
		Var2: 2,
		Child: SimpleChild{
			Name:      "theChild",
			Property1: 3, // should be ignored
			Property2: 4.5,
		},
	}

	if err := enc.Encode(testStruct); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

Var1: v1
variable_2: 2
Child:
  Name: theChild
  Property2: 4.5
Example (SimpleMap)

Encode test with two maps. Map: map string -> struct StructMap: map struct -> int

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// SimpleChild test struct
type SimpleChild struct {
	Name      string
	Property1 uint64  `human:"-"`
	Property2 float64 `human:",omitempty"`
}

// MapTest test struct
type MapTest struct {
	Val1      uint64
	Map       map[string]SimpleChild
	StructMap map[SimpleChild]uint8
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}

	child1 := SimpleChild{
		Name:      "Person1",
		Property2: 4.5,
		Property1: 0, // should be ignored
	}

	child2 := SimpleChild{
		Name: "Person2",
	}
	stringMap := map[string]SimpleChild{
		"One": child1,
		"Two": child2,
	}
	structMap := map[SimpleChild]uint8{
		child1: 1,
		child2: 2,
	}
	testStruct := MapTest{
		Map:       stringMap,
		StructMap: structMap,
	}

	if err := enc.Encode(testStruct); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

Val1: 0
Map:
  * One: Name: Person1
    Property2: 4.5
  * Two: Name: Person2
StructMap:
  * {Person1 0 4.5}: 1
  * {Person2 0 0}: 2
Example (SimpleOmitEmpty)

Encode test with simple test struct to test encode with ignored fields and omit if field is empty

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// SimpleChild test struct
type SimpleChild struct {
	Name      string
	Property1 uint64  `human:"-"`
	Property2 float64 `human:",omitempty"`
}

// SimpleTest test struct
type SimpleTest struct {
	Var1  string
	Var2  int `human:"variable_2"`
	Child SimpleChild
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}
	testStruct := SimpleTest{
		Var1: "v1",
		Var2: 2,
		Child: SimpleChild{
			Name:      "theChild",
			Property1: 3, // should be ignored
			Property2: 0, // empty, should be omitted
		},
	}

	if err := enc.Encode(testStruct); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

Var1: v1
variable_2: 2
Child:
  Name: theChild
Example (SimpleSlice)

Encode test with slice of integers and slice of structs

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// SimpleChild test struct
type SimpleChild struct {
	Name      string
	Property1 uint64  `human:"-"`
	Property2 float64 `human:",omitempty"`
}

// SliceTest test struct
type SliceTest struct {
	IntSlice    []int
	StructSlice []SimpleChild
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}

	child1 := SimpleChild{
		Name:      "Person1",
		Property2: 4.5,
		Property1: 0, // should be ignored
	}

	child2 := SimpleChild{
		Name: "Person2",
	}
	structSlice := []SimpleChild{child1, child2}
	testStruct := SliceTest{
		IntSlice:    []int{1, 2, 3, 4, 5},
		StructSlice: structSlice,
	}

	if err := enc.Encode(testStruct); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}
}
Output:

IntSlice:
  * 1
  * 2
  * 3
  * 4
  * 5
StructSlice:
  * Name: Person1
    Property2: 4.5
  * Name: Person2
Example (StructMapSlice)

Encode test with slice of maps and each map is of type map[string]int

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// MapSliceTest test struct with slice of maps
type MapSliceTest struct {
	StructMapSlice []map[string]int
}

func main() {
	enc, err := human.NewEncoder(os.Stdout, human.OptionListSymbols("+", "-"))
	if err != nil {
		return
	}

	mapSliceElement1 := map[string]int{
		"one":                            1,
		"two":                            2,
		"tenthousandonehundredfourtytwo": 10142,
	}
	slice := []map[string]int{mapSliceElement1, mapSliceElement1}
	testStruct := MapSliceTest{
		StructMapSlice: slice,
	}

	if err := enc.Encode(testStruct); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

StructMapSlice:
  +
    - one: 1
    - tenthousandonehundredfourtytwo: 10142
    - two: 2
  +
    - one: 1
    - tenthousandonehundredfourtytwo: 10142
    - two: 2
Example (TagError)

Encode test with invalid tag name

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// TagFailTest test struct
type TagFailTest struct {
	Test int `human:"&§/$"`
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}

	testStruct := TagFailTest{
		Test: 1,
	}

	if err := enc.Encode(testStruct); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

ERROR: 1 error occurred:

* Invalid tag: '&§/$'
Example (TextMarshaler)

Encode test with TextMarshaler implemented by field

package main

import (
	"fmt"
	"net"
	"os"

	human "github.com/anexia-it/go-human"
)

// TextMarshalerTest test struct
type TextMarshalerTest struct {
	Ip net.IP
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}

	addr := TextMarshalerTest{
		Ip: net.ParseIP("127.0.0.1"),
	}
	if err := enc.Encode(addr); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

Ip: 127.0.0.1
Example (TextMarshalerPointer)
package main

import (
	"fmt"
	"net"
	"os"

	human "github.com/anexia-it/go-human"
)

type TextMarshalerPointerTest struct {
	Ip *net.IP
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}

	ip := net.ParseIP("127.0.0.1")
	addr := TextMarshalerPointerTest{
		Ip: &ip,
	}
	if err := enc.Encode(addr); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

Ip: 127.0.0.1
Example (UnexportedField)

Encode test with unexported field

package main

import (
	"fmt"
	"os"

	human "github.com/anexia-it/go-human"
)

// UnexportedFieldTest test struct
type UnexportedFieldTest struct {
	unexported int
	Text       string
}

func main() {
	enc, err := human.NewEncoder(os.Stdout)
	if err != nil {
		return
	}

	// unexported int field is ignored
	testStruct := UnexportedFieldTest{
		Text: "test",
	}

	if err := enc.Encode(testStruct); err != nil {
		fmt.Printf("ERROR: %s\n", err.Error())
		return
	}

}
Output:

Text: test

type FlushableBuffer

type FlushableBuffer struct {
	*bytes.Buffer
	// contains filtered or unexported fields
}

FlushableBuffer wraps around an io.Writer and provides the ability to lazily write to the buffer when the Flush method is called.

This allows for delay of actual writing to the io.Buffer, providing an interface similar to the io.Writer.

func NewFlushableBuffer

func NewFlushableBuffer(stream io.Writer) *FlushableBuffer

NewFlushableBuffer creates a new FlushableBuffer for a given io.Writer

func (*FlushableBuffer) Flush

func (b *FlushableBuffer) Flush() (n int, err error)

Flush writes the data written to the FlushableBuffer to the underlying stream.

type InvalidTag

type InvalidTag struct {
	// contains filtered or unexported fields
}

InvalidTag is an error that indicates that the tag value was invalid

func IsInvalidTag

func IsInvalidTag(err error) (*InvalidTag, bool)

IsInvalidTag checks if the given error is an InvalidTag error and returns the InvalidTag error along with a boolean that defines if it is indeed an invalid tag error. The returned *InvalidTag may be nil, if the flag is false

func (*InvalidTag) Error

func (it *InvalidTag) Error() string

Error returns the error string and causes InvalidTag to implement the error interface

func (*InvalidTag) Tag

func (it *InvalidTag) Tag() string

Tag returns the tag name

type Option

type Option func(*Encoder) error

Option defines the function type of Encoder options

func OptionIndent

func OptionIndent(indent uint) Option

OptionIndent specifies the used indentation

func OptionListSymbols

func OptionListSymbols(listSymbols ...string) Option

OptionListSymbols specifies the list symbols

func OptionTagName

func OptionTagName(tagName string) Option

OptionTagName specifies the tag name

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL