diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba5c552 --- /dev/null +++ b/README.md @@ -0,0 +1,445 @@ +# Notation - Print Go objects + +This package can be used to print (or sprint) Go objects with optional wrapping (indentation) and optional type +information for debugging purposes. + +### Alternatives + +Notation is similar to the following great, more established and mature packages: + +- [go-spew](https://github.com/davecgh/go-spew/) +- [litter](https://github.com/sanity-io/litter) +- [utter](https://github.com/kortschak/utter) + +Notation differs from these primarily in the 'flavour' of printing and the package interface. + +### Installation + +`go get github.com/aryszka/notation` + +### Usage + +Pass in the Go object(s) to be printed to one of the notation functions. These functions can be categorized into +four groups: + +- **Println:** the Println type functions print Go objects to stderr with an additional newline at the end. +- **Print:** similar to Println but without the extra newline. +- **Fprint:** the Fprint type functions print Go objects to an arbitrary writer passed in as an argument. +- **Sprint:** the Sprint type functions return the string representation of Go objects instead of printing them. + +The format and the verbosity can be controlled with the suffixed variants of the above functions. By default, +the input arguments are printed without types on a single line. The following suffixes are available: + +- **`w`**: wrap the output based on Go-style indentation +- **`t`**: print moderately verbose type information, omitting where it can be trivially inferred +- **`v`**: print verbose type information + +When **`t`** or **`v`** are used together with **`w`**, they must follow **`w`**. The suffixes **`t`** and +**`v`** cannot be used together. + +Wrapping is **not eager**. It doesn't wrap a line when it can fit on 72 columns. It also tolerates longer lines up +to 112 columns, when the output is considered to be more readable that way. This means very simple Go objects +are not wrapped even with the **`w`** variant of the functions. + +For the available functions, see also the [godoc](https://godoc.org/github.com/aryszka/notation). + +### Example + +Assuming to have the required types defined, if we do the following: + +```go +b := bike{ + frame: frame{ + fork: fork{}, + saddlePost: saddlePost{}, + }, + driveTrain: driveTrain{ + bottomBracket: bracket{}, + crank: crank{wheels: 2}, + brakes: []brake{{discSize: 160}, {discSize: 140}}, + derailleurs: []derailleur{{gears: 2}, {gears: 11}}, + cassette: cassette{wheels: 11}, + chain: chain{}, + levers: []lever{{true}, {true}}, + }, + wheels: []wheel{{size: 70}, {size: 70}}, + handlebar: handlebar{}, + saddle: saddle{}, +} + +b.frame.fork.wheel = &b.wheels[0] +b.frame.fork.handlebar = &b.handlebar +b.frame.fork.handlebar.levers = []*lever{&b.driveTrain.levers[0], &b.driveTrain.levers[1]} +b.frame.fork.frontBrake = &b.driveTrain.brakes[0] +b.frame.saddlePost.saddle = &b.saddle +b.frame.bottomBracket = &b.driveTrain.bottomBracket +b.frame.frontDerailleur = &b.driveTrain.derailleurs[0] +b.frame.rearDerailleur = &b.driveTrain.derailleurs[1] +b.frame.rearBrake = &b.driveTrain.brakes[1] +b.frame.rearWheel = &b.wheels[1] +b.frame.bottomBracket.crank = &b.driveTrain.crank +b.frame.bottomBracket.crank.chain = &b.driveTrain.chain +b.frame.rearWheel.cassette = &b.driveTrain.cassette +b.frame.rearWheel.cassette.chain = &b.driveTrain.chain + +s := notation.Sprintw(b) +``` + +We get the following string: + +```go +{ + frame: { + fork: { + wheel: {size: 70, cassette: nil}, + handlebar: {levers: []{{withShift: true}, {withShift: true}}}, + frontBrake: {discSize: 160}, + }, + saddlePost: {saddle: {}}, + bottomBracket: {crank: {wheels: 2, chain: {}}}, + frontDerailleur: {gears: 2}, + rearDerailleur: {gears: 11}, + rearBrake: {discSize: 140}, + rearWheel: {size: 70, cassette: {wheels: 11, chain: {}}}, + }, + driveTrain: { + bottomBracket: {crank: {wheels: 2, chain: {}}}, + crank: {wheels: 2, chain: {}}, + brakes: []{{discSize: 160}, {discSize: 140}}, + derailleurs: []{{gears: 2}, {gears: 11}}, + cassette: {wheels: 11, chain: {}}, + chain: {}, + levers: []{{withShift: true}, {withShift: true}}, + }, + wheels: []{{size: 70, cassette: nil}, {size: 70, cassette: {wheels: 11, chain: {}}}}, + handlebar: {levers: []{{withShift: true}, {withShift: true}}}, + saddle: {}, +} +``` + +Using `notation.Sprintwv` instead of `notation.Sprintw`, we would get the following string: + +```go +bike{ + frame: frame{ + fork: fork{ + wheel: *wheel{size: float64(70), cassette: (*cassette)(nil)}, + handlebar: *handlebar{ + levers: []*lever{ + *lever{withShift: bool(true)}, + *lever{withShift: bool(true)}, + }, + }, + frontBrake: *brake{discSize: float64(160)}, + }, + saddlePost: saddlePost{saddle: *saddle{}}, + bottomBracket: *bracket{crank: *crank{wheels: int(2), chain: *chain{}}}, + frontDerailleur: *derailleur{gears: int(2)}, + rearDerailleur: *derailleur{gears: int(11)}, + rearBrake: *brake{discSize: float64(140)}, + rearWheel: *wheel{ + size: float64(70), + cassette: *cassette{wheels: int(11), chain: *chain{}}, + }, + }, + driveTrain: driveTrain{ + bottomBracket: bracket{crank: *crank{wheels: int(2), chain: *chain{}}}, + crank: crank{wheels: int(2), chain: *chain{}}, + brakes: []brake{brake{discSize: float64(160)}, brake{discSize: float64(140)}}, + derailleurs: []derailleur{ + derailleur{gears: int(2)}, + derailleur{gears: int(11)}, + }, + cassette: cassette{wheels: int(11), chain: *chain{}}, + chain: chain{}, + levers: []lever{lever{withShift: bool(true)}, lever{withShift: bool(true)}}, + }, + wheels: []wheel{ + wheel{size: float64(70), cassette: (*cassette)(nil)}, + wheel{size: float64(70), cassette: *cassette{wheels: int(11), chain: *chain{}}}, + }, + handlebar: handlebar{levers: []*lever{*lever{withShift: bool(true)}, *lever{withShift: bool(true)}}}, + saddle: saddle{}, +} +``` + +### Subtleties + +Notation doesn't provide configuration options, we can just pick the preferred function and call it with the +objects to be printed. The following details describe some of the behavior to be expected in case of various +input objects. + +##### Numbers + +Numbers are printed based on the `fmt` package's default formatting. When printing with moderate type +information, the type for the `int`, default witdth signed integers, will be omitted. + +```go +i := 42 +notation.Printlnt(i) +``` + +Output: + +```go +42 +``` + +##### Strings + +When printing strings, by default they are escaped using the `strconv.Quote` function. However, when wrapping +long strings, and the string contains a newline and doesn't contain a backquote '`', then the string is printed +as a raw string literal, delimited by '`'. + +Short string: + +```go +s := `foobar +baz` +notation.Printlnw(s) +``` + +Output: + +```go +"foobar\nbaz" +``` + +Long string: + +```go +s := `The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps +over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy +dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog.` + +notation.Println(s) +``` + +Output: + +```go +`The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps +over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy +dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog.` +``` + +##### Arrays/Slices + +Slices are are printed by printing their elements between braces, prefixed either by '[]' or the type of the +slice. Example: + +```go +l := []int{1, 2, 3} +notation.Println(l) +``` + +Output: + +```go +[]{1, 2, 3} +``` + +To differentiate arrays from slices, arrays are always prefixed with their type or square brackets containing +the length of the array: + +```go +a := [...]{1, 2, 3} +notation.Println(a) +``` + +Output: + +```go +[3]{1, 2, 3} +``` + +##### Bytes + +When the type of a slice is `uint8`, or an alias of it, e.g. `byte`, then it is printed as []byte, with the hexa +representation of its bytes: + +```go +b := []byte( +`The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps +over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy +dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog.`, +) + +notation.Printlnwt(b) +``` + +Output: + +```go +[]byte{ + 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a + 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f + 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f + 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 + 20 64 6f 67 2e 20 54 68 65 0a 71 75 69 63 6b 20 62 72 6f 77 6e + 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c + 61 7a 79 20 64 6f 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 + 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 + 65 20 6c 61 7a 79 20 64 6f 67 2e 20 54 68 65 20 71 75 69 63 6b + 20 62 72 6f 77 6e 0a 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 + 20 74 68 65 20 6c 61 7a 79 20 64 6f 67 2e 20 54 68 65 20 71 75 + 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f + 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f 67 2e 20 54 68 65 + 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 + 73 0a 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f 67 2e 20 + 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a + 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f + 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f + 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 + 0a 64 6f 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e + 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c + 61 7a 79 20 64 6f 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 + 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 + 65 20 6c 61 7a 79 20 64 6f 67 2e 20 54 68 65 0a 71 75 69 63 6b + 20 62 72 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 + 20 74 68 65 20 6c 61 7a 79 20 64 6f 67 2e +} +``` + +##### Maps + +Maps are printed with their entries sorted by the string representation of their keys: + +```go +m := map[string]int{"b": 1, "c": 2, "a": 3} +notation.Println(m) +``` + +Output: + +```go +map{"a": 3, "b": 1, "c": 2} +``` + +This way a map is printed always the same way. If, for a reason, this is undesired, then this behavior can be +disabled via the `MAPSORT=0` environment variable. + +##### Hidden values: channels, functions + +Certain values, like channels and functions are printed without expanding their internals, e.g. channee state or +function body. When printing with types, the signature of these objects is printed: + +```go +f := func(int) int { return 42 } +notation.Println(f) +``` + +Output: + +```go +func() +``` + +With types: + +```go +f := func(int) int { return 42 } +notation.Printlnt(f) +``` + +Output: + +```go +func(int) int +``` + +##### Wrapping + +Whe 'w' variant of the printing functions wraps the output with Go style indentation where the lines would be +too long otherwise. The wrapping is not eager, it only aims for fitting the lines on 72 columns. To measure the +indentation, it assumes 8 character width tabs. In certain cases, it tolerates longer lines up to 112 columns, +when the output would probably more readable that way. Of course, readability is subjective. + +As a hidden feature, when it's really necessary, it is possible to change the above control values via +environmetn variables. TABWIDTH controls the measuring of the indentation. LINEWIDTH sets the aimed column width +of the printed lines. LINEWIDTH1 sets the tolerated threshold for those lines that are allowed to exceed the +default line width. E.g. if somebody uses two-character wide tabs in their console, they can use the package +like this: + +``` +TABWIDTH=2 go test -v -count 1 +``` + +As a consequence, it is also possible to wrap all lines: + +``` +TABWIDTH=0 LINEWIDTH=0 LINEWIDTH1=0 go test -v -count 1 +``` + +##### Types + +Using the 't' or 'v' suffixed variants of the printing functions, notation prints the types together with the +values. When the name of a type is available, the name is printed instead of the literal representation of the +type. The package path is not printed. + +Named type: + +```go +type t struct{foo int} +v := t{42} +notation.Printlnt(v) +``` + +Output: + +```go +t{foo: 42} +``` + +Unnamed type: + +```go +v := struct{foo int}{42} +notation.Printlnt(v) +``` + +Output: + +```go +struct{foo int}{foo: 42} +``` + +Using the 't' suffixed variants of the printing functions, displaying only moderately verbose type information, +the types of certain values is omitted, where it can be inferred from the context: + +```go +v := []struct{foo int}{{42}, {84}} +notation.Printlnt(os.Stdout, v) +``` + +Output: + +```go +[]struct{foo int}{{foo: 42}, {foo: 84}} +``` + +##### Cyclic references + +Cyclic references are detected based on an approach similar to the one in the stdlib's reflect.DeepEqual +function. Such occurences are displayed in the output with references: + +```go +l := []interface{}{"foo"} +l[0] = l +notation.Fprint(os.Stdout, l) +``` + +Output: + +```go +r0=[]{r0} +``` diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..e46b908 --- /dev/null +++ b/example_test.go @@ -0,0 +1,292 @@ +package notation_test + +import ( + "os" + + "github.com/aryszka/notation" +) + +type bike struct { + frame frame + driveTrain driveTrain + wheels []wheel + handlebar handlebar + saddle saddle +} +type frame struct { + fork fork + saddlePost saddlePost + bottomBracket *bracket + frontDerailleur *derailleur + rearDerailleur *derailleur + rearBrake *brake + rearWheel *wheel +} +type driveTrain struct { + bottomBracket bracket + crank crank + brakes []brake + derailleurs []derailleur + cassette cassette + chain chain + levers []lever +} +type wheel struct { + size float64 + cassette *cassette +} +type handlebar struct{ levers []*lever } +type saddle struct{} +type fork struct { + wheel *wheel + handlebar *handlebar + frontBrake *brake +} +type saddlePost struct{ saddle *saddle } +type bracket struct{ crank *crank } +type derailleur struct{ gears int } +type brake struct{ discSize float64 } +type crank struct { + wheels int + chain *chain +} +type cassette struct { + wheels int + chain *chain +} +type chain struct{} +type lever struct{ withShift bool } + +func Example() { + b := bike{ + frame: frame{ + fork: fork{}, + saddlePost: saddlePost{}, + }, + driveTrain: driveTrain{ + bottomBracket: bracket{}, + crank: crank{wheels: 2}, + brakes: []brake{{discSize: 160}, {discSize: 140}}, + derailleurs: []derailleur{{gears: 2}, {gears: 11}}, + cassette: cassette{wheels: 11}, + chain: chain{}, + levers: []lever{{true}, {true}}, + }, + wheels: []wheel{{size: 70}, {size: 70}}, + handlebar: handlebar{}, + saddle: saddle{}, + } + + b.frame.fork.wheel = &b.wheels[0] + b.frame.fork.handlebar = &b.handlebar + b.frame.fork.handlebar.levers = []*lever{&b.driveTrain.levers[0], &b.driveTrain.levers[1]} + b.frame.fork.frontBrake = &b.driveTrain.brakes[0] + b.frame.saddlePost.saddle = &b.saddle + b.frame.bottomBracket = &b.driveTrain.bottomBracket + b.frame.frontDerailleur = &b.driveTrain.derailleurs[0] + b.frame.rearDerailleur = &b.driveTrain.derailleurs[1] + b.frame.rearBrake = &b.driveTrain.brakes[1] + b.frame.rearWheel = &b.wheels[1] + b.frame.bottomBracket.crank = &b.driveTrain.crank + b.frame.bottomBracket.crank.chain = &b.driveTrain.chain + b.frame.rearWheel.cassette = &b.driveTrain.cassette + b.frame.rearWheel.cassette.chain = &b.driveTrain.chain + + notation.Fprintw(os.Stdout, b) + + // Output: + // + // { + // frame: { + // fork: { + // wheel: {size: 70, cassette: nil}, + // handlebar: {levers: []{{withShift: true}, {withShift: true}}}, + // frontBrake: {discSize: 160}, + // }, + // saddlePost: {saddle: {}}, + // bottomBracket: {crank: {wheels: 2, chain: {}}}, + // frontDerailleur: {gears: 2}, + // rearDerailleur: {gears: 11}, + // rearBrake: {discSize: 140}, + // rearWheel: {size: 70, cassette: {wheels: 11, chain: {}}}, + // }, + // driveTrain: { + // bottomBracket: {crank: {wheels: 2, chain: {}}}, + // crank: {wheels: 2, chain: {}}, + // brakes: []{{discSize: 160}, {discSize: 140}}, + // derailleurs: []{{gears: 2}, {gears: 11}}, + // cassette: {wheels: 11, chain: {}}, + // chain: {}, + // levers: []{{withShift: true}, {withShift: true}}, + // }, + // wheels: []{{size: 70, cassette: nil}, {size: 70, cassette: {wheels: 11, chain: {}}}}, + // handlebar: {levers: []{{withShift: true}, {withShift: true}}}, + // saddle: {}, + // } +} + +func Example_int() { + i := 42 + notation.Fprintt(os.Stdout, i) + + // Output: + // + // 42 +} + +func Example_short_string() { + s := `foobar +baz` + notation.Fprintw(os.Stdout, s) + + // Output: + // + // "foobar\nbaz" +} + +func Example_long_string() { + s := `The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps +over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy +dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog.` + + notation.Fprintw(os.Stdout, s) + + // Output: + // + // `The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The + // quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown + // fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps + // over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy + // dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The + // quick brown fox jumps over the lazy dog.` +} + +func Example_slice() { + l := []int{1, 2, 3} + notation.Fprint(os.Stdout, l) + + // Output: + // + // []{1, 2, 3} +} + +func Example_array() { + a := [...]int{1, 2, 3} + notation.Fprint(os.Stdout, a) + + // Output: + // + // [3]{1, 2, 3} +} + +func Example_bytes() { + b := []byte( + `The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps +over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy +dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The +quick brown fox jumps over the lazy dog.`, + ) + + notation.Fprintwt(os.Stdout, b) + + // Output: + // + // []byte{ + // 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a + // 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f + // 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f + // 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 + // 20 64 6f 67 2e 20 54 68 65 0a 71 75 69 63 6b 20 62 72 6f 77 6e + // 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c + // 61 7a 79 20 64 6f 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 + // 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 + // 65 20 6c 61 7a 79 20 64 6f 67 2e 20 54 68 65 20 71 75 69 63 6b + // 20 62 72 6f 77 6e 0a 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 + // 20 74 68 65 20 6c 61 7a 79 20 64 6f 67 2e 20 54 68 65 20 71 75 + // 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f + // 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f 67 2e 20 54 68 65 + // 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 + // 73 0a 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f 67 2e 20 + // 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a + // 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f + // 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f + // 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 + // 0a 64 6f 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e + // 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c + // 61 7a 79 20 64 6f 67 2e 20 54 68 65 20 71 75 69 63 6b 20 62 72 + // 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 + // 65 20 6c 61 7a 79 20 64 6f 67 2e 20 54 68 65 0a 71 75 69 63 6b + // 20 62 72 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 + // 20 74 68 65 20 6c 61 7a 79 20 64 6f 67 2e + // } +} + +func Example_maps_sorted_by_keys() { + m := map[string]int{"b": 1, "c": 2, "a": 3} + notation.Fprint(os.Stdout, m) + + // Output: + // + // map{"a": 3, "b": 1, "c": 2} +} + +func Example_function() { + f := func(int) int { return 42 } + notation.Fprint(os.Stdout, f) + + // Output: + // + // func() +} + +func Example_function_signature() { + f := func(int) int { return 42 } + notation.Fprintt(os.Stdout, f) + + // Output: + // + // func(int) int +} + +func Example_named_type() { + type t struct{ foo int } + v := t{42} + notation.Fprintt(os.Stdout, v) + + // Output: + // + // t{foo: 42} +} + +func Example_unnamed() { + v := struct{ foo int }{42} + notation.Fprintt(os.Stdout, v) + + // Output: + // + // struct{foo int}{foo: 42} +} + +func Example_type_inferred() { + v := []struct{ foo int }{{42}, {84}} + notation.Fprintt(os.Stdout, v) + + // Output: + // + // []struct{foo int}{{foo: 42}, {foo: 84}} +} + +func Example_cyclic_reference() { + l := []interface{}{"foo"} + l[0] = l + notation.Fprint(os.Stdout, l) + + // Output: + // + // r0=[]{r0} +} diff --git a/notation.go b/notation.go index fdba41e..2eb59b7 100644 --- a/notation.go +++ b/notation.go @@ -1,3 +1,7 @@ +/* +Package notation can be used to print (or sprint) Go objects with optional wrapping (pretty printing) and +optional type information. +*/ package notation import ( @@ -199,98 +203,148 @@ func sprintValues(o opts, v []interface{}) string { return b.String() } +// Fprint prints the provided objects to the provided writer. When multiple objects are printed, they'll be +// separated by a space. func Fprint(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, none, v) } +// Fprintw prints the provided objects to the provided writer, with wrapping (pretty-printing) where necessary. +// When multiple objects are printed, they'll be separated by a newline. func Fprintw(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, wrap, v) } +// Fprintt prints the provided objects to the provided writer with moderate type information. When multiple +// objects are printed, they'll be separated by a space. func Fprintt(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, types, v) } +// Fprintwt prints the provided objects to the provided writer, with wrapping (pretty-printing) where necessary, +// and with moderate type information. When multiple objects are printed, they'll be separated by a newline. func Fprintwt(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, wrap|types, v) } +// Fprintv prints the provided objects to the provided writer with verbose type information. When multiple +// objects are printed, they'll be separated by a space. func Fprintv(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, allTypes, v) } +// Fprintwv prints the provided objects to the provided writer, with wrapping (pretty-printing) where necessary, +// and with verbose type information. When multiple objects are printed, they'll be separated by a newline. func Fprintwv(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, wrap|allTypes, v) } +// Print prints the provided objects to stderr. When multiple objects are printed, they'll be separated by a +// space. func Print(v ...interface{}) (int, error) { return printValues(none, v) } +// Printw prints the provided objects to stderr, with wrapping (pretty-printing) where necessary. When multiple +// objects are printed, they'll be separated by a newline. func Printw(v ...interface{}) (int, error) { return printValues(wrap, v) } +// Printt prints the provided objects to stderr with moderate type information. When multiple objects are +// printed, they'll be separated by a space. func Printt(v ...interface{}) (int, error) { return printValues(types, v) } +// Printwt prints the provided objects to stderr, with wrapping (pretty-printing) where necessary, and with +// moderate type information. When multiple objects are printed, they'll be separated by a newline. func Printwt(v ...interface{}) (int, error) { return printValues(wrap|types, v) } +// Printv prints the provided objects to stderr with verbose type information. When multiple objects are +// printed, they'll be separated by a space. func Printv(v ...interface{}) (int, error) { return printValues(allTypes, v) } +// Printwv prints the provided objects to stderr, with wrapping (pretty-printing) where necessary, and with +// verbose type information. When multiple objects are printed, they'll be separated by a newline. func Printwv(v ...interface{}) (int, error) { return printValues(wrap|allTypes, v) } +// Println prints the provided objects to stderr with a closing newline. When multiple objects are printed, +// they'll be separated by a space. func Println(v ...interface{}) (int, error) { return printlnValues(none, v) } +// Printlnw prints the provided objects to stderr with a closing newline, with wrapping (pretty-printing) where +// necessary. When multiple objects are printed, they'll be separated by a newline. func Printlnw(v ...interface{}) (int, error) { return printlnValues(wrap, v) } +// Printlnt prints the provided objects to stderr with a closing newline, and with moderate type information. When +// multiple objects are printed, they'll be separated by a space. func Printlnt(v ...interface{}) (int, error) { return printlnValues(types, v) } +// Printlnwt prints the provided objects to stderr with a closing newline, with wrapping (pretty-printing) where +// necessary, and with moderate type information. When multiple objects are printed, they'll be separated by a +// newline. func Printlnwt(v ...interface{}) (int, error) { return printlnValues(wrap|types, v) } +// Printlnv prints the provided objects to stderr with a closing newline, and with verbose type information. When +// multiple objects are printed, they'll be separated by a space. func Printlnv(v ...interface{}) (int, error) { return printlnValues(allTypes, v) } +// Printlnwv prints the provided objects to stderr with a closing newline, with wrapping (pretty-printing) where +// necessary, and with verbose type information. When multiple objects are printed, they'll be separated by a +// newline. func Printlnwv(v ...interface{}) (int, error) { return printlnValues(wrap|allTypes, v) } +// Sprint returns the string representation of the Go objects. When multiple objects are provided, they'll be +// seprated by a space. func Sprint(v ...interface{}) string { return sprintValues(none, v) } +// Sprint returns the string representation of the Go objects, with wrapping (pretty-printing) where necessary. +// When multiple objects are provided, they'll be seprated by a newline. func Sprintw(v ...interface{}) string { return sprintValues(wrap, v) } +// Sprint returns the string representation of the Go objects, with moderate type information. When multiple +// objects are provided, they'll be seprated by a space. func Sprintt(v ...interface{}) string { return sprintValues(types, v) } +// Sprint returns the string representation of the Go objects, with wrapping (pretty-printing) where necessary, +// and with moderate type information. When multiple objects are provided, they'll be seprated by a newline. func Sprintwt(v ...interface{}) string { return sprintValues(wrap|types, v) } +// Sprint returns the string representation of the Go objects, with verbose type information. When multiple +// objects are provided, they'll be seprated by a space. func Sprintv(v ...interface{}) string { return sprintValues(allTypes, v) } +// Sprint returns the string representation of the Go objects, with wrapping (pretty-printing) where necessary, +// and with verbose type information. When multiple objects are provided, they'll be seprated by a newline. func Sprintwv(v ...interface{}) string { return sprintValues(wrap|allTypes, v) } diff --git a/notation_test.go b/notation_test.go index 8adc1c2..5a9866e 100644 --- a/notation_test.go +++ b/notation_test.go @@ -12,21 +12,21 @@ func TestPrint(t *testing.T) { e string }{{ p: Print, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `{foo: 42}`, }, { p: Printw, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `{ foo: 42, }`, }, { p: Printt, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `struct{foo int}{foo: 42}`, }, { p: Printwt, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `struct{ foo int }{ @@ -34,11 +34,11 @@ func TestPrint(t *testing.T) { }`, }, { p: Printv, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `struct{foo int}{foo: int(42)}`, }, { p: Printwv, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `struct{ foo int }{ @@ -76,22 +76,22 @@ func TestPrintln(t *testing.T) { e string }{{ p: Println, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: "{foo: 42}\n", }, { p: Printlnw, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `{ foo: 42, } `, }, { p: Printlnt, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: "struct{foo int}{foo: 42}\n", }, { p: Printlnwt, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `struct{ foo int }{ @@ -100,11 +100,11 @@ func TestPrintln(t *testing.T) { `, }, { p: Printlnv, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: "struct{foo int}{foo: int(42)}\n", }, { p: Printlnwv, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, e: `struct{ foo int }{ @@ -113,7 +113,7 @@ func TestPrintln(t *testing.T) { `, }, { p: Println, - o: struct{foo int}{42}, + o: struct{ foo int }{42}, f: true, }} { defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")() diff --git a/reflect.go b/reflect.go index 548d609..4dc74b3 100644 --- a/reflect.go +++ b/reflect.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "sort" + "strconv" "strings" ) @@ -136,12 +137,7 @@ func reflectMap(o opts, p *pending, r reflect.Value) node { return reflectNil(o, true, r) } - var ( - nkeys []node - skeys []string - ) - - // TODO: simplify this when no sorting is required + var skeys []string items := wrapper{sep: ", ", suffix: ","} itemOpts := o | skipTypes keys := r.MapKeys() @@ -150,7 +146,6 @@ func reflectMap(o opts, p *pending, r reflect.Value) node { for _, key := range keys { var b bytes.Buffer nk := reflectValue(itemOpts, p, key) - nkeys = append(nkeys, nk) wr := writer{w: &b} fprint(&wr, 0, nk) skey := b.String() @@ -208,32 +203,7 @@ func reflectList(o opts, p *pending, r reflect.Value) node { func reflectString(o opts, r reflect.Value) node { sv := r.String() - b := []byte(sv) - e := make([]byte, 0, len(b)) - for _, c := range b { - switch c { - case '\\': - e = append(e, '\\', '\\') - case '"': - e = append(e, '\\', '"') - case '\b': - e = append(e, '\\', 'b') - case '\f': - e = append(e, '\\', 'f') - case '\n': - e = append(e, '\\', 'n') - case '\r': - e = append(e, '\\', 'r') - case '\t': - e = append(e, '\\', 't') - case '\v': - e = append(e, '\\', 'v') - default: - e = append(e, c) - } - } - - s := str{val: fmt.Sprintf("\"%s\"", string(e))} + s := str{val: strconv.Quote(sv)} if !strings.Contains(sv, "`") && strings.Contains(sv, "\n") { s.raw = fmt.Sprintf("`%s`", sv) }