diff --git a/node/basicnode/map_test.go b/node/basicnode/map_test.go index 03e17d9d..b10a5d51 100644 --- a/node/basicnode/map_test.go +++ b/node/basicnode/map_test.go @@ -21,9 +21,11 @@ func TestMap(t *testing.T) { func BenchmarkMapStrInt_3n_AssembleStandard(b *testing.B) { tests.SpecBenchmarkMapStrInt_3n_AssembleStandard(b, basicnode.Prototype.Map) } + func BenchmarkMapStrInt_3n_AssembleEntry(b *testing.B) { tests.SpecBenchmarkMapStrInt_3n_AssembleEntry(b, basicnode.Prototype.Map) } + func BenchmarkMapStrInt_3n_Iteration(b *testing.B) { tests.SpecBenchmarkMapStrInt_3n_Iteration(b, basicnode.Prototype.Map) } @@ -31,9 +33,11 @@ func BenchmarkMapStrInt_3n_Iteration(b *testing.B) { func BenchmarkMapStrInt_25n_AssembleStandard(b *testing.B) { tests.SpecBenchmarkMapStrInt_25n_AssembleStandard(b, basicnode.Prototype.Map) } + func BenchmarkMapStrInt_25n_AssembleEntry(b *testing.B) { tests.SpecBenchmarkMapStrInt_25n_AssembleEntry(b, basicnode.Prototype.Map) } + func BenchmarkMapStrInt_25n_Iteration(b *testing.B) { tests.SpecBenchmarkMapStrInt_25n_Iteration(b, basicnode.Prototype.Map) } @@ -41,6 +45,7 @@ func BenchmarkMapStrInt_25n_Iteration(b *testing.B) { func BenchmarkSpec_Marshal_Map3StrInt(b *testing.B) { tests.BenchmarkSpec_Marshal_Map3StrInt(b, basicnode.Prototype.Map) } + func BenchmarkSpec_Marshal_MapNStrMap3StrInt(b *testing.B) { tests.BenchmarkSpec_Marshal_MapNStrMap3StrInt(b, basicnode.Prototype.Map) } @@ -48,6 +53,7 @@ func BenchmarkSpec_Marshal_MapNStrMap3StrInt(b *testing.B) { func BenchmarkSpec_Unmarshal_Map3StrInt(b *testing.B) { tests.BenchmarkSpec_Unmarshal_Map3StrInt(b, basicnode.Prototype.Map) } + func BenchmarkSpec_Unmarshal_MapNStrMap3StrInt(b *testing.B) { tests.BenchmarkSpec_Unmarshal_MapNStrMap3StrInt(b, basicnode.Prototype.Map) } @@ -123,9 +129,9 @@ func TestMapBuilder(t *testing.T) { actual := printer.Sprint(mapNode) expect := `map{ - string{"cat"}: string{"meow"} - string{"dog"}: string{"bark"} - string{"eel"}: string{"zap"} + string{"cat"}:string{"meow"}, + string{"dog"}:string{"bark"}, + string{"eel"}:string{"zap"} }` qt.Check(t, expect, qt.Equals, actual) diff --git a/printer/printer.go b/printer/printer.go index 7febe068..101505fd 100644 --- a/printer/printer.go +++ b/printer/printer.go @@ -15,21 +15,21 @@ import ( // All printer configuration will be the default; // links will be printed, and will not be traversed. func Print(n datamodel.Node) { - Config{}.Print(n) + Config{Indentation: []byte{'\t'}}.Print(n) } // Sprint returns a textual description of the node tree. // All printer configuration will be the default; // links will be printed, and will not be traversed. func Sprint(n datamodel.Node) string { - return Config{}.Sprint(n) + return Config{Indentation: []byte{'\t'}}.Sprint(n) } // Fprint accepts an io.Writer to which a textual description of the node tree will be written. // All printer configuration will be the default; // links will be printed, and will not be traversed. func Fprint(w io.Writer, n datamodel.Node) { - Config{}.Fprint(w, n) + Config{Indentation: []byte{'\t'}}.Fprint(w, n) } // Print emits a textual description of the node tree straight to stdout. @@ -50,7 +50,6 @@ func (cfg Config) Sprint(n datamodel.Node) string { // The configuration structure this method is attached to can be used to specified details for how the printout will be formatted. func (cfg Config) Fprint(w io.Writer, n datamodel.Node) { pr := printBuf{w, cfg} - pr.Config.init() pr.doString(0, printState_normal, n) } @@ -61,23 +60,16 @@ type Config struct { Abbreviate bool // If set, the indentation to use. - // If nil, it will be treated as a default "\t". + // If nil, no indentation will be used and newlines will be omitted. If set to an empty slice, + // newlines will be used but no indentation. Setting to `[]byte{'\t'}` is a common choice. Indentation []byte - // Probably does exactly what you think it does. + // If set, the string to use for the start of each line. StartingIndent []byte - // Set to true if you like verbosity, I guess. - // If false, strings will only have kind+type markings if they're typed. - // - // Not yet supported. - AlwaysMarkStrings bool - - // Set to true if you want type info to be skipped for any type that's in the Prelude - // (e.g. instead of `string{` seeing only `string{` is preferred, etc). - // - // Not yet supported. - ElidePreludeTypeInfo bool + // If set to true, scalar values will be omitted from the output. This is useful for representing + // the structure of a graph without the data. + OmitScalarValues bool // Set to true if you want maps to use "complex"-style printouts: // meaning they will print their keys on separate lines than their values, @@ -93,12 +85,6 @@ type Config struct { UseMapComplexStyleOnType map[schema.TypeName]bool } -func (cfg *Config) init() { - if cfg.Indentation == nil { - cfg.Indentation = []byte{'\t'} - } -} - // oneline decides if a value should be flatted into printing on a single, // or if it's allowed to spread out over multiple lines. // Note that this will not be asked if something outside of a value has already declared it's @@ -163,6 +149,12 @@ func (z *printBuf) doIndent(indentLevel int) { } } +func (z *printBuf) doNewline() { + if z.Config.StartingIndent != nil || z.Config.Indentation != nil { + z.wr.Write([]byte{'\n'}) + } +} + const ( printState_normal uint8 = iota printState_isKey // may sometimes entersen or stringify things harder. @@ -221,9 +213,8 @@ func (z *printBuf) doString(indentLevel int, printState uint8, n datamodel.Node) // Also, because it's possible for structs to be keys in a map themselves, they potentially need oneline emission. // Or, to customize emission in another direction if being a key in a map that's printing in "complex" mode. // FUTURE: there should also probably be some way to configure instructions to use their representation form instead. - oneline := - printState == printState_isCmplxValue || - printState != printState_isCmplxKey && z.Config.oneline(tn.Type(), printState == printState_isKey) + oneline := printState == printState_isCmplxValue || + printState != printState_isCmplxKey && z.Config.oneline(tn.Type(), printState == printState_isKey) deepen := 1 if printState == printState_isCmplxKey { deepen = 2 @@ -234,7 +225,7 @@ func (z *printBuf) doString(indentLevel int, printState uint8, n datamodel.Node) } z.writeString("{") if !oneline && n.Length() > 0 { - z.writeString("\n") + z.doNewline() } for itr := n.MapIterator(); !itr.Done(); { k, v, _ := itr.Next() @@ -243,14 +234,13 @@ func (z *printBuf) doString(indentLevel int, printState uint8, n datamodel.Node) } fn, _ := k.AsString() z.writeString(fn) - z.writeString(": ") + z.writeString(":") z.doString(indentLevel+deepen, childState, v) - if oneline { - if !itr.Done() { - z.writeString(", ") - } - } else { - z.writeString("\n") + if !itr.Done() { + z.writeString(",") + } + if !oneline { + z.doNewline() } } if !oneline { @@ -295,7 +285,7 @@ func (z *printBuf) doString(indentLevel int, printState uint8, n datamodel.Node) } z.writeString("{") if n.Length() > 0 { - z.writeString("\n") + z.doNewline() } else { z.writeString("}") return @@ -306,20 +296,23 @@ func (z *printBuf) doString(indentLevel int, printState uint8, n datamodel.Node) z.doIndent(indentLevel + 1) z.writeString("!! map iteration step yielded error: ") z.writeString(err.Error()) - z.writeString("\n") + z.doNewline() break } z.doString(indentLevel+1, childKeyState, k) - z.writeString(": ") + z.writeString(":") z.doString(indentLevel+1, printState_isValue, v) - z.writeString("\n") + if !itr.Done() { + z.writeString(",") + } + z.doNewline() } z.doIndent(indentLevel) z.writeString("}") case datamodel.Kind_List: z.writeString("{") if n.Length() > 0 { - z.writeString("\n") + z.doNewline() } else { z.writeString("}") return @@ -330,14 +323,17 @@ func (z *printBuf) doString(indentLevel int, printState uint8, n datamodel.Node) z.doIndent(indentLevel + 1) z.writeString("!! list iteration step yielded error: ") z.writeString(err.Error()) - z.writeString("\n") + z.doNewline() break } z.doIndent(indentLevel + 1) z.writeString(strconv.FormatInt(idx, 10)) - z.writeString(": ") + z.writeString(":") z.doString(indentLevel+1, printState_isValue, v) - z.writeString("\n") + if !itr.Done() { + z.writeString(",") + } + z.doNewline() } z.doIndent(indentLevel) z.writeString("}") @@ -352,31 +348,41 @@ func (z *printBuf) doString(indentLevel int, printState uint8, n datamodel.Node) } z.writeString("}") case datamodel.Kind_Int: - x, _ := n.AsInt() - z.writeString("{") - z.writeString(strconv.FormatInt(x, 10)) - z.writeString("}") + if !z.Config.OmitScalarValues { + x, _ := n.AsInt() + z.writeString("{") + z.writeString(strconv.FormatInt(x, 10)) + z.writeString("}") + } case datamodel.Kind_Float: - x, _ := n.AsFloat() - z.writeString("{") - z.writeString(strconv.FormatFloat(x, 'f', -1, 64)) - z.writeString("}") + if !z.Config.OmitScalarValues { + x, _ := n.AsFloat() + z.writeString("{") + z.writeString(strconv.FormatFloat(x, 'f', -1, 64)) + z.writeString("}") + } case datamodel.Kind_String: - x, _ := n.AsString() - z.writeString("{") - z.writeString(strconv.QuoteToGraphic(x)) - z.writeString("}") + if !z.Config.OmitScalarValues { + x, _ := n.AsString() + z.writeString("{") + z.writeString(strconv.QuoteToGraphic(x)) + z.writeString("}") + } case datamodel.Kind_Bytes: - x, _ := n.AsBytes() - z.writeString("{") - dst := make([]byte, hex.EncodedLen(len(x))) - hex.Encode(dst, x) - z.writeString(string(dst)) - z.writeString("}") + if !z.Config.OmitScalarValues { + x, _ := n.AsBytes() + z.writeString("{") + dst := make([]byte, hex.EncodedLen(len(x))) + hex.Encode(dst, x) + z.writeString(string(dst)) + z.writeString("}") + } case datamodel.Kind_Link: - x, _ := n.AsLink() - z.writeString("{") - z.writeString(x.String()) - z.writeString("}") + if !z.Config.OmitScalarValues { + x, _ := n.AsLink() + z.writeString("{") + z.writeString(x.String()) + z.writeString("}") + } } } diff --git a/printer/printer_test.go b/printer/printer_test.go index 9a53b617..ba33efd9 100644 --- a/printer/printer_test.go +++ b/printer/printer_test.go @@ -39,21 +39,66 @@ func TestSimpleData(t *testing.T) { }) qt.Check(t, Sprint(n), qt.CmpEquals(), testutil.Dedent(` map{ - string{"some key"}: string{"some value"} - string{"another key"}: string{"another value"} - string{"nested map"}: map{ - string{"deeper entries"}: string{"deeper values"} - string{"more deeper entries"}: string{"more deeper values"} - } - string{"nested list"}: list{ - 0: int{1} - 1: int{2} - } - string{"list with float"}: list{ - 0: float{3.4} + string{"some key"}:string{"some value"}, + string{"another key"}:string{"another value"}, + string{"nested map"}:map{ + string{"deeper entries"}:string{"deeper values"}, + string{"more deeper entries"}:string{"more deeper values"} + }, + string{"nested list"}:list{ + 0:int{1}, + 1:int{2} + }, + string{"list with float"}:list{ + 0:float{3.4} } }`, )) + t.Run("compact-form", func(t *testing.T) { + qt.Check(t, Config{}.Sprint(n), qt.CmpEquals(), `map{string{"some key"}:string{"some value"},string{"another key"}:string{"another value"},string{"nested map"}:map{string{"deeper entries"}:string{"deeper values"},string{"more deeper entries"}:string{"more deeper values"}},string{"nested list"}:list{0:int{1},1:int{2}},string{"list with float"}:list{0:float{3.4}}}`) + }) + t.Run("custom-indentation", func(t *testing.T) { + qt.Check(t, Config{Indentation: []byte("==>")}.Sprint(n), qt.CmpEquals(), testutil.Dedent(` + map{ + ==>string{"some key"}:string{"some value"}, + ==>string{"another key"}:string{"another value"}, + ==>string{"nested map"}:map{ + ==>==>string{"deeper entries"}:string{"deeper values"}, + ==>==>string{"more deeper entries"}:string{"more deeper values"} + ==>}, + ==>string{"nested list"}:list{ + ==>==>0:int{1}, + ==>==>1:int{2} + ==>}, + ==>string{"list with float"}:list{ + ==>==>0:float{3.4} + ==>} + }`, + )) + }) + t.Run("line-prefix-indentation", func(t *testing.T) { + qt.Check(t, Config{StartingIndent: []byte("->"), Indentation: []byte{'*'}}.Sprint(n), qt.CmpEquals(), testutil.Dedent(` + ->map{ + ->*string{"some key"}:string{"some value"}, + ->*string{"another key"}:string{"another value"}, + ->*string{"nested map"}:map{ + ->**string{"deeper entries"}:string{"deeper values"}, + ->**string{"more deeper entries"}:string{"more deeper values"} + ->*}, + ->*string{"nested list"}:list{ + ->**0:int{1}, + ->**1:int{2} + ->*}, + ->*string{"list with float"}:list{ + ->**0:float{3.4} + ->*} + ->}`, + )) + }) + + t.Run("omit-scalars", func(t *testing.T) { + qt.Check(t, Config{OmitScalarValues: true}.Sprint(n), qt.CmpEquals(), `map{string:string,string:string,string:map{string:string,string:string},string:list{0:int,1:int},string:list{0:float}}`) + }) }) t.Run("map-with-link-and-bytes", func(t *testing.T) { @@ -73,17 +118,17 @@ func TestSimpleData(t *testing.T) { }) qt.Check(t, Sprint(n), qt.CmpEquals(), testutil.Dedent(` map{ - string{"some key"}: link{bafkqabiaaebagba} - string{"another key"}: string{"another value"} - string{"nested map"}: map{ - string{"deeper entries"}: string{"deeper values"} - string{"more deeper entries"}: link{bafkqabiaaebagba} - string{"yet another deeper entries"}: bytes{66697368} - } - string{"nested list"}: list{ - 0: bytes{67686f7469} - 1: int{1} - 2: link{bafkqabiaaebagba} + string{"some key"}:link{bafkqabiaaebagba}, + string{"another key"}:string{"another value"}, + string{"nested map"}:map{ + string{"deeper entries"}:string{"deeper values"}, + string{"more deeper entries"}:link{bafkqabiaaebagba}, + string{"yet another deeper entries"}:bytes{66697368} + }, + string{"nested list"}:list{ + 0:bytes{67686f7469}, + 1:int{1}, + 2:link{bafkqabiaaebagba} } }`, )) @@ -112,10 +157,10 @@ func TestTypedData(t *testing.T) { n := bindnode.Wrap(&FooBar{"x", "y", []byte("zed"), testLink}, ts.TypeByName("FooBar")) qt.Check(t, Sprint(n), qt.CmpEquals(), testutil.Dedent(` struct{ - foo: string{"x"} - bar: string{"y"} - baz: bytes{7a6564} - jazz: link{bafkqabiaaebagba} + foo:string{"x"}, + bar:string{"y"}, + baz:bytes{7a6564}, + jazz:link{bafkqabiaaebagba} }`, )) }) @@ -145,8 +190,8 @@ func TestTypedData(t *testing.T) { }, ts.TypeByName("WowMap")) qt.Check(t, Sprint(n), qt.CmpEquals(), testutil.Dedent(` map{ - struct{foo: string{"x"}, bar: string{"y"}}: string{"a"} - struct{foo: string{"z"}, bar: string{"z"}}: string{"b"} + struct{foo:string{"x"},bar:string{"y"}}:string{"a"}, + struct{foo:string{"z"},bar:string{"z"}}:string{"b"} }`, )) }) @@ -184,48 +229,50 @@ func TestTypedData(t *testing.T) { }, ts.TypeByName("WowMap")) t.Run("complex-keys-in-effect", func(t *testing.T) { cfg := Config{ + Indentation: []byte{'\t'}, UseMapComplexStyleAlways: true, } qt.Check(t, cfg.Sprint(n), qt.CmpEquals(), testutil.Dedent(` map{ struct{ - foo: string{"x"} - bar: struct{ - baz: string{"y"} + foo:string{"x"}, + bar:struct{ + baz:string{"y"} + }, + baz:struct{ + baz:string{"y"} } - baz: struct{ - baz: string{"y"} - } - }: struct{ - baz: string{"a"} - } + }:struct{ + baz:string{"a"} + }, struct{ - foo: string{"z"} - bar: struct{ - baz: string{"z"} + foo:string{"z"}, + bar:struct{ + baz:string{"z"} + }, + baz:struct{ + baz:string{"z"} } - baz: struct{ - baz: string{"z"} - } - }: struct{ - baz: string{"b"} + }:struct{ + baz:string{"b"} } }`, )) }) t.Run("complex-keys-in-disabled", func(t *testing.T) { cfg := Config{ + Indentation: []byte{'\t'}, UseMapComplexStyleOnType: map[schema.TypeName]bool{ "WowMap": false, }, } qt.Check(t, cfg.Sprint(n), qt.CmpEquals(), testutil.Dedent(` map{ - struct{foo: string{"x"}, bar: struct{baz: string{"y"}}, baz: struct{baz: string{"y"}}}: struct{ - baz: string{"a"} - } - struct{foo: string{"z"}, bar: struct{baz: string{"z"}}, baz: struct{baz: string{"z"}}}: struct{ - baz: string{"b"} + struct{foo:string{"x"},bar:struct{baz:string{"y"}},baz:struct{baz:string{"y"}}}:struct{ + baz:string{"a"} + }, + struct{foo:string{"z"},bar:struct{baz:string{"z"}},baz:struct{baz:string{"z"}}}:struct{ + baz:string{"b"} } }`, ))