From 87c1cdb78766912c3433e934ea63dfe1de696cd1 Mon Sep 17 00:00:00 2001 From: Don Wonders Date: Mon, 26 Aug 2024 22:32:16 -0400 Subject: [PATCH] add --with-comments to preserve comments when returning an attribute --- cmd/attribute.go | 7 +- cmd/attribute_test.go | 41 ++++++++++ editor/client_test.go | 2 +- editor/operator_derive_test.go | 85 ++++++++++++--------- editor/sink_attribute_get.go | 12 +-- editor/sink_attribute_get_test.go | 123 +++++++++++++++++------------- 6 files changed, 172 insertions(+), 98 deletions(-) diff --git a/cmd/attribute.go b/cmd/attribute.go index 91cf8f7..0e1e0ce 100644 --- a/cmd/attribute.go +++ b/cmd/attribute.go @@ -44,6 +44,10 @@ Arguments: RunE: runAttributeGetCmd, } + flags := cmd.Flags() + flags.Bool("with-comments", false, "return comments along with attribute value") + _ = viper.BindPFlag("attribute.get.withcomments", flags.Lookup("with-comments")) + return cmd } @@ -55,11 +59,12 @@ func runAttributeGetCmd(cmd *cobra.Command, args []string) error { address := args[0] file := viper.GetString("file") update := viper.GetBool("update") + withcomments := viper.GetBool("attribute.get.withcomments") if update { return errors.New("The update flag is not allowed") } - sink := editor.NewAttributeGetSink(address) + sink := editor.NewAttributeGetSink(address, withcomments) c := newDefaultClient(cmd) return c.Derive(file, sink) } diff --git a/cmd/attribute_test.go b/cmd/attribute_test.go index 20dabca..41acb20 100644 --- a/cmd/attribute_test.go +++ b/cmd/attribute_test.go @@ -12,6 +12,13 @@ func TestAttributeGet(t *testing.T) { key = "services/hoge/dev/terraform.tfstate" } } +locals { + map = { + # comment + attribute = "bar" + } + attribute = "foo" # comment +} ` cases := []struct { @@ -44,6 +51,40 @@ func TestAttributeGet(t *testing.T) { ok: false, want: "", }, + { + name: "with comments", + args: []string{"--with-comments", "locals.map"}, + ok: true, + want: `{ + # comment + attribute = "bar" + } +`, + }, + { + name: "without comments", + args: []string{"locals.map"}, + ok: true, + want: `{ + + attribute = "bar" + } +`, + }, + // does not pass at current + // { + // name: "single with comments", + // args: []string{"--with-comments", "locals.attribute"}, + // ok: true, + // want: `"foo" #comment`, + // }, + { + name: "single without comments", + args: []string{"locals.attribute"}, + ok: true, + want: `"foo" +`, + }, } for _, tc := range cases { diff --git a/editor/client_test.go b/editor/client_test.go index bb96d8d..de12e3d 100644 --- a/editor/client_test.go +++ b/editor/client_test.go @@ -124,7 +124,7 @@ a1 = v1 a0 = v3 a2 = v2 ` - sink := NewAttributeGetSink("a0") + sink := NewAttributeGetSink("a0", false) cases := []struct { name string diff --git a/editor/operator_derive_test.go b/editor/operator_derive_test.go index 704352d..81928c1 100644 --- a/editor/operator_derive_test.go +++ b/editor/operator_derive_test.go @@ -7,11 +7,12 @@ import ( func TestOperatorDeriveApply(t *testing.T) { cases := []struct { - name string - src string - address string - ok bool - want string + name string + src string + address string + withcomments bool + ok bool + want string }{ { name: "match", @@ -19,9 +20,10 @@ func TestOperatorDeriveApply(t *testing.T) { a0 = v0 a1 = v1 `, - address: "a0", - ok: true, - want: "v0\n", + address: "a0", + withcomments: false, + ok: true, + want: "v0\n", }, { name: "not found", @@ -29,9 +31,10 @@ a1 = v1 a0 = v0 a1 = v1 `, - address: "a2", - ok: true, - want: "", + address: "a2", + withcomments: false, + ok: true, + want: "", }, { name: "syntax error", @@ -46,7 +49,7 @@ b1 { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - o := NewDeriveOperator(NewAttributeGetSink(tc.address)) + o := NewDeriveOperator(NewAttributeGetSink(tc.address, tc.withcomments)) output, err := o.Apply([]byte(tc.src), "test") if tc.ok && err != nil { t.Fatalf("unexpected err = %s", err) @@ -66,11 +69,12 @@ b1 { func TestDeriveStream(t *testing.T) { cases := []struct { - name string - src string - address string - ok bool - want string + name string + src string + address string + withcomments bool + ok bool + want string }{ { name: "match", @@ -78,9 +82,10 @@ func TestDeriveStream(t *testing.T) { a0 = v0 a1 = v1 `, - address: "a0", - ok: true, - want: "v0\n", + address: "a0", + withcomments: false, + ok: true, + want: "v0\n", }, { name: "not found", @@ -88,9 +93,10 @@ a1 = v1 a0 = v0 a1 = v1 `, - address: "a2", - ok: true, - want: "", + address: "a2", + withcomments: false, + ok: true, + want: "", }, } @@ -98,7 +104,7 @@ a1 = v1 t.Run(tc.name, func(t *testing.T) { inStream := bytes.NewBufferString(tc.src) outStream := new(bytes.Buffer) - sink := NewAttributeGetSink(tc.address) + sink := NewAttributeGetSink(tc.address, tc.withcomments) err := DeriveStream(inStream, outStream, "test", sink) if tc.ok && err != nil { t.Fatalf("unexpected err = %s", err) @@ -118,12 +124,13 @@ a1 = v1 func TestDeriveFile(t *testing.T) { cases := []struct { - name string - src string - address string - value string - ok bool - want string + name string + src string + address string + withcomments bool + value string + ok bool + want string }{ { name: "match", @@ -131,9 +138,10 @@ func TestDeriveFile(t *testing.T) { a0 = v0 a1 = v1 `, - address: "a0", - ok: true, - want: "v0\n", + address: "a0", + withcomments: false, + ok: true, + want: "v0\n", }, { name: "not found", @@ -141,9 +149,10 @@ a1 = v1 a0 = v0 a1 = v1 `, - address: "a2", - ok: true, - want: "", + address: "a2", + withcomments: false, + ok: true, + want: "", }, } @@ -151,7 +160,7 @@ a1 = v1 t.Run(tc.name, func(t *testing.T) { path := setupTestFile(t, tc.src) outStream := new(bytes.Buffer) - sink := NewAttributeGetSink(tc.address) + sink := NewAttributeGetSink(tc.address, tc.withcomments) err := DeriveFile(path, outStream, sink) if tc.ok && err != nil { t.Fatalf("unexpected err = %s", err) @@ -175,7 +184,7 @@ a1 = v1 } func TestDeriveFileNotFound(t *testing.T) { - sink := NewAttributeGetSink("foo") + sink := NewAttributeGetSink("foo", false) outStream := new(bytes.Buffer) err := DeriveFile("not_found", outStream, sink) if err == nil { diff --git a/editor/sink_attribute_get.go b/editor/sink_attribute_get.go index 7611ecd..114f802 100644 --- a/editor/sink_attribute_get.go +++ b/editor/sink_attribute_get.go @@ -11,14 +11,16 @@ import ( // AttributeGetSink is a sink implementation for getting a value of attribute. type AttributeGetSink struct { address string + withcomments bool } var _ Sink = (*AttributeGetSink)(nil) // NewAttributeGetSink creates a new instance of AttributeGetSink. -func NewAttributeGetSink(address string) Sink { +func NewAttributeGetSink(address string, withcomments bool) Sink { return &AttributeGetSink{ address: address, + withcomments: withcomments, } } @@ -35,7 +37,7 @@ func (s *AttributeGetSink) Sink(inFile *hclwrite.File) ([]byte, error) { } // treat expr as a string without interpreting its meaning. - out, err := GetAttributeValueAsString(attr) + out, err := GetAttributeValueAsString(attr, s.withcomments) if err != nil { return []byte{}, err } @@ -183,15 +185,15 @@ func longestMatchingLabels(labels []string, prefix []string) []string { // GetAttributeValueAsString returns a value of Attribute as string. // There is no way to get value as string directly, // so we parses tokens of Attribute and build string representation. -func GetAttributeValueAsString(attr *hclwrite.Attribute) (string, error) { +func GetAttributeValueAsString(attr *hclwrite.Attribute, withcomments bool) (string, error) { // find TokenEqual expr := attr.Expr() exprTokens := expr.BuildTokens(nil) - + // append tokens until find TokenComment var valueTokens hclwrite.Tokens for _, t := range exprTokens { - if t.Type == hclsyntax.TokenComment { + if t.Type == hclsyntax.TokenComment && !withcomments { t.Bytes = []byte("\n") t.SpacesBefore = 0 } diff --git a/editor/sink_attribute_get_test.go b/editor/sink_attribute_get_test.go index 545abeb..0d9b472 100644 --- a/editor/sink_attribute_get_test.go +++ b/editor/sink_attribute_get_test.go @@ -8,11 +8,12 @@ import ( func TestAttributeGetSink(t *testing.T) { cases := []struct { - name string - src string - address string - ok bool - want string + name string + src string + address string + withcomments bool + ok bool + want string }{ { name: "simple top level attribute", @@ -20,18 +21,20 @@ func TestAttributeGetSink(t *testing.T) { a0 = v0 a1 = v1 `, - address: "a0", - ok: true, - want: "v0\n", + address: "a0", + withcomments: false, + ok: true, + want: "v0\n", }, { name: "quoted literal is as it is and should not be unquoted", src: ` a0 = "v0" `, - address: "a0", - ok: true, - want: "\"v0\"\n", + address: "a0", + withcomments: false, + ok: true, + want: "\"v0\"\n", }, { name: "not found", @@ -39,9 +42,10 @@ a0 = "v0" a0 = v0 a1 = v1 `, - address: "hoge", - ok: true, - want: "", + address: "hoge", + withcomments: false, + ok: true, + want: "", }, { name: "attribute with comments", @@ -50,9 +54,10 @@ a1 = v1 a0 = v0 // inline comment a1 = v1 `, - address: "a0", - ok: true, - want: "v0\n", + address: "a0", + withcomments: false, + ok: true, + want: "v0\n", }, { name: "multiline attribute with comments", @@ -69,8 +74,9 @@ a1 = [ ] a2 = v2 `, - address: "a1", - ok: true, + address: "a1", + withcomments: false, + ok: true, want: `[ "val1", "val2", @@ -87,9 +93,10 @@ a2 = v2 a0 = v0 a0 = v1 `, - address: "a0", - ok: false, - want: "", + address: "a0", + withcomments: false, + ok: false, + want: "", }, { name: "attribute in block", @@ -98,9 +105,10 @@ b1 { a1 = v1 } `, - address: "b1.a1", - ok: true, - want: "v1\n", + address: "b1.a1", + withcomments: false, + ok: true, + want: "v1\n", }, { name: "attribute in block with a label", @@ -109,9 +117,10 @@ b1 "l1" { a1 = v1 } `, - address: "b1.l1.a1", - ok: true, - want: "v1\n", + address: "b1.l1.a1", + withcomments: false, + ok: true, + want: "v1\n", }, { name: "attribute in block with multiple labels", @@ -129,9 +138,10 @@ b1 "l1" "l2" "l3" { a1 = v3 } `, - address: "b1.l1.l2.a1", - ok: true, - want: "v2\n", + address: "b1.l1.l2.a1", + withcomments: false, + ok: true, + want: "v2\n", }, { name: "attribute in nested block", @@ -143,9 +153,10 @@ b1 { } } `, - address: "b1.b2.a2", - ok: true, - want: "v2\n", + address: "b1.b2.a2", + withcomments: false, + ok: true, + want: "v2\n", }, { name: "attribute in nested block (extra labels)", @@ -157,9 +168,10 @@ b1 "l1" { } } `, - address: "b1.b2.a2", - ok: true, - want: "", + address: "b1.b2.a2", + withcomments: false, + ok: true, + want: "", }, { name: "labels take precedence over nested blocks", @@ -171,9 +183,10 @@ b1 "b2" { } } `, - address: "b1.b2.a1", - ok: true, - want: "v1\n", + address: "b1.b2.a1", + withcomments: false, + ok: true, + want: "v1\n", }, { name: "attribute in multi level nested block", @@ -188,9 +201,10 @@ b1 { } } `, - address: "b1.b2.b3.a3", - ok: true, - want: "v3\n", + address: "b1.b2.b3.a3", + withcomments: false, + ok: true, + want: "v3\n", }, { name: "attribute in nested block with labels", @@ -205,9 +219,10 @@ b1 { } } `, - address: "b1.b2.b3.a2", - ok: true, - want: "v2\n", + address: "b1.b2.b3.a2", + withcomments: false, + ok: true, + want: "v2\n", }, { name: "attribute in duplicated blocks", @@ -219,9 +234,10 @@ b1 "l1" "l2" { a1 = v2 } `, - address: "b1.l1.l2.a1", - ok: true, - want: "v1\n", + address: "b1.l1.l2.a1", + withcomments: false, + ok: true, + want: "v1\n", }, { name: "attribute in block with a escaped address", @@ -230,15 +246,16 @@ b1 "l.1" { a1 = v1 } `, - address: `b1.l\.1.a1`, - ok: true, - want: "v1\n", + address: `b1.l\.1.a1`, + withcomments: false, + ok: true, + want: "v1\n", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - o := NewDeriveOperator(NewAttributeGetSink(tc.address)) + o := NewDeriveOperator(NewAttributeGetSink(tc.address, tc.withcomments)) output, err := o.Apply([]byte(tc.src), "test") if tc.ok && err != nil { t.Fatalf("unexpected err = %s", err)