Skip to content

Commit 8e731ac

Browse files
committed
Added "debug-node-info" flag for inspecting yq AST
1 parent d0c897f commit 8e731ac

File tree

8 files changed

+262
-3
lines changed

8 files changed

+262
-3
lines changed

cmd/constant.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package cmd
22

33
var unwrapScalarFlag = newUnwrapFlag()
44

5+
var printNodeInfo = false
6+
57
var unwrapScalar = false
68

79
var writeInplace = false

cmd/evaluate_sequence_command.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
105105
}
106106

107107
printer := yqlib.NewPrinter(encoder, printerWriter)
108+
109+
if printNodeInfo {
110+
printer = yqlib.NewNodeInfoPrinter(printerWriter)
111+
}
112+
108113
if nulSepOutput {
109114
printer.SetNulSepOutput(true)
110115
}

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ yq -P -oy sample.json
9999
}
100100

101101
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
102+
rootCmd.PersistentFlags().BoolVarP(&printNodeInfo, "debug-node-info", "", false, "debug node info")
102103

103104
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "(deprecated) output as json. Set indent to 0 to print json in one line.")
104105
err := rootCmd.PersistentFlags().MarkDeprecated("tojson", "please use -o=json instead")

examples/data1.yaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
Foo: 3
2-
apple: 1
3-
bar: 2
1+
# 001
2+
---
3+
abc: # 001
4+
- 1 # one
5+
- 2 # two
6+
7+
---
8+
def # 002

pkg/yqlib/candidate_node.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ func createScalarNode(value interface{}, stringValue string) *CandidateNode {
5353
return node
5454
}
5555

56+
type NodeInfo struct {
57+
Kind string `yaml:"kind"`
58+
Style string `yaml:"style,omitempty"`
59+
Anchor string `yaml:"anchor,omitempty"`
60+
Tag string `yaml:"tag,omitempty"`
61+
HeadComment string `yaml:"headComment,omitempty"`
62+
LineComment string `yaml:"lineComment,omitempty"`
63+
FootComment string `yaml:"footComment,omitempty"`
64+
Value string `yaml:"value,omitempty"`
65+
Line int `yaml:"line,omitempty"`
66+
Column int `yaml:"column,omitempty"`
67+
Content []*NodeInfo `yaml:"content,omitempty"`
68+
}
69+
5670
type CandidateNode struct {
5771
Kind Kind
5872
Style Style
@@ -451,3 +465,64 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP
451465
n.LineComment = other.LineComment
452466
}
453467
}
468+
469+
func (n *CandidateNode) ConvertToNodeInfo() *NodeInfo {
470+
info := &NodeInfo{
471+
Kind: kindToString(n.Kind),
472+
Style: styleToString(n.Style),
473+
Anchor: n.Anchor,
474+
Tag: n.Tag,
475+
HeadComment: n.HeadComment,
476+
LineComment: n.LineComment,
477+
FootComment: n.FootComment,
478+
Value: n.Value,
479+
Line: n.Line,
480+
Column: n.Column,
481+
}
482+
if len(n.Content) > 0 {
483+
info.Content = make([]*NodeInfo, len(n.Content))
484+
for i, child := range n.Content {
485+
info.Content[i] = child.ConvertToNodeInfo()
486+
}
487+
}
488+
return info
489+
}
490+
491+
// Helper functions to convert Kind and Style to string for NodeInfo
492+
func kindToString(k Kind) string {
493+
switch k {
494+
case SequenceNode:
495+
return "SequenceNode"
496+
case MappingNode:
497+
return "MappingNode"
498+
case ScalarNode:
499+
return "ScalarNode"
500+
case AliasNode:
501+
return "AliasNode"
502+
default:
503+
return "Unknown"
504+
}
505+
}
506+
507+
func styleToString(s Style) string {
508+
var styles []string
509+
if s&TaggedStyle != 0 {
510+
styles = append(styles, "TaggedStyle")
511+
}
512+
if s&DoubleQuotedStyle != 0 {
513+
styles = append(styles, "DoubleQuotedStyle")
514+
}
515+
if s&SingleQuotedStyle != 0 {
516+
styles = append(styles, "SingleQuotedStyle")
517+
}
518+
if s&LiteralStyle != 0 {
519+
styles = append(styles, "LiteralStyle")
520+
}
521+
if s&FoldedStyle != 0 {
522+
styles = append(styles, "FoldedStyle")
523+
}
524+
if s&FlowStyle != 0 {
525+
styles = append(styles, "FlowStyle")
526+
}
527+
return strings.Join(styles, ",")
528+
}

pkg/yqlib/candidate_node_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,47 @@ func TestCandidateNodeAddKeyValueChild(t *testing.T) {
160160
test.AssertResult(t, key.IsMapKey, true)
161161

162162
}
163+
164+
func TestConvertToNodeInfo(t *testing.T) {
165+
child := &CandidateNode{
166+
Kind: ScalarNode,
167+
Style: DoubleQuotedStyle,
168+
Tag: "!!str",
169+
Value: "childValue",
170+
Line: 2,
171+
Column: 3,
172+
}
173+
parent := &CandidateNode{
174+
Kind: MappingNode,
175+
Style: TaggedStyle,
176+
Tag: "!!map",
177+
Value: "",
178+
Line: 1,
179+
Column: 1,
180+
Content: []*CandidateNode{child},
181+
HeadComment: "head",
182+
LineComment: "line",
183+
FootComment: "foot",
184+
Anchor: "anchor",
185+
}
186+
info := parent.ConvertToNodeInfo()
187+
test.AssertResult(t, "MappingNode", info.Kind)
188+
test.AssertResult(t, "TaggedStyle", info.Style)
189+
test.AssertResult(t, "!!map", info.Tag)
190+
test.AssertResult(t, "head", info.HeadComment)
191+
test.AssertResult(t, "line", info.LineComment)
192+
test.AssertResult(t, "foot", info.FootComment)
193+
test.AssertResult(t, "anchor", info.Anchor)
194+
test.AssertResult(t, 1, info.Line)
195+
test.AssertResult(t, 1, info.Column)
196+
if len(info.Content) != 1 {
197+
t.Fatalf("Expected 1 child, got %d", len(info.Content))
198+
}
199+
childInfo := info.Content[0]
200+
test.AssertResult(t, "ScalarNode", childInfo.Kind)
201+
test.AssertResult(t, "DoubleQuotedStyle", childInfo.Style)
202+
test.AssertResult(t, "!!str", childInfo.Tag)
203+
test.AssertResult(t, "childValue", childInfo.Value)
204+
test.AssertResult(t, 2, childInfo.Line)
205+
test.AssertResult(t, 3, childInfo.Column)
206+
}

pkg/yqlib/printer_node_info.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package yqlib
2+
3+
import (
4+
"bufio"
5+
"container/list"
6+
"io"
7+
8+
"go.yaml.in/yaml/v3"
9+
)
10+
11+
type nodeInfoPrinter struct {
12+
printerWriter PrinterWriter
13+
appendixReader io.Reader
14+
printedMatches bool
15+
}
16+
17+
func NewNodeInfoPrinter(printerWriter PrinterWriter) Printer {
18+
return &nodeInfoPrinter{
19+
printerWriter: printerWriter,
20+
}
21+
}
22+
23+
func (p *nodeInfoPrinter) SetNulSepOutput(_ bool) {
24+
}
25+
26+
func (p *nodeInfoPrinter) SetAppendix(reader io.Reader) {
27+
p.appendixReader = reader
28+
}
29+
30+
func (p *nodeInfoPrinter) PrintedAnything() bool {
31+
return p.printedMatches
32+
}
33+
34+
func (p *nodeInfoPrinter) PrintResults(matchingNodes *list.List) error {
35+
36+
for el := matchingNodes.Front(); el != nil; el = el.Next() {
37+
mappedDoc := el.Value.(*CandidateNode)
38+
writer, errorWriting := p.printerWriter.GetWriter(mappedDoc)
39+
if errorWriting != nil {
40+
return errorWriting
41+
}
42+
bytes, err := yaml.Marshal(mappedDoc.ConvertToNodeInfo())
43+
if err != nil {
44+
return err
45+
}
46+
if _, err := writer.Write(bytes); err != nil {
47+
return err
48+
}
49+
if _, err := writer.Write([]byte("\n")); err != nil {
50+
return err
51+
}
52+
p.printedMatches = true
53+
if err := writer.Flush(); err != nil {
54+
return err
55+
}
56+
}
57+
58+
if p.appendixReader != nil {
59+
writer, err := p.printerWriter.GetWriter(nil)
60+
if err != nil {
61+
return err
62+
}
63+
64+
log.Debug("Piping appendix reader...")
65+
betterReader := bufio.NewReader(p.appendixReader)
66+
_, err = io.Copy(writer, betterReader)
67+
if err != nil {
68+
return err
69+
}
70+
if err := writer.Flush(); err != nil {
71+
return err
72+
}
73+
}
74+
75+
return nil
76+
}

pkg/yqlib/printer_node_info_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package yqlib
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"container/list"
7+
"strings"
8+
"testing"
9+
10+
"github.com/mikefarah/yq/v4/test"
11+
)
12+
13+
func TestNodeInfoPrinter_PrintResults(t *testing.T) {
14+
// Create a simple CandidateNode
15+
node := &CandidateNode{
16+
Kind: ScalarNode,
17+
Style: DoubleQuotedStyle,
18+
Tag: "!!str",
19+
Value: "hello world",
20+
Line: 5,
21+
Column: 7,
22+
HeadComment: "head",
23+
LineComment: "line",
24+
FootComment: "foot",
25+
Anchor: "anchor",
26+
}
27+
listNodes := list.New()
28+
listNodes.PushBack(node)
29+
30+
var output bytes.Buffer
31+
writer := bufio.NewWriter(&output)
32+
printer := NewNodeInfoPrinter(NewSinglePrinterWriter(writer))
33+
err := printer.PrintResults(listNodes)
34+
writer.Flush()
35+
if err != nil {
36+
t.Fatalf("PrintResults error: %v", err)
37+
}
38+
39+
outStr := output.String()
40+
// Check for key NodeInfo fields in YAML output using substring checks
41+
test.AssertResult(t, true, strings.Contains(outStr, "kind: ScalarNode"))
42+
test.AssertResult(t, true, strings.Contains(outStr, "style: DoubleQuotedStyle"))
43+
test.AssertResult(t, true, strings.Contains(outStr, "tag: '!!str'"))
44+
test.AssertResult(t, true, strings.Contains(outStr, "value: hello world"))
45+
test.AssertResult(t, true, strings.Contains(outStr, "line: 5"))
46+
test.AssertResult(t, true, strings.Contains(outStr, "column: 7"))
47+
test.AssertResult(t, true, strings.Contains(outStr, "headComment: head"))
48+
test.AssertResult(t, true, strings.Contains(outStr, "lineComment: line"))
49+
test.AssertResult(t, true, strings.Contains(outStr, "footComment: foot"))
50+
test.AssertResult(t, true, strings.Contains(outStr, "anchor: anchor"))
51+
}

0 commit comments

Comments
 (0)