Skip to content

Commit

Permalink
Start implementing HCL parser
Browse files Browse the repository at this point in the history
  • Loading branch information
TomWright committed Oct 31, 2024
1 parent 11c723e commit a6074e8
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 11 deletions.
1 change: 1 addition & 0 deletions cmd/dasel/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/tomwright/dasel/v3/internal/cli"
_ "github.com/tomwright/dasel/v3/parsing/d"
_ "github.com/tomwright/dasel/v3/parsing/hcl"
_ "github.com/tomwright/dasel/v3/parsing/json"
_ "github.com/tomwright/dasel/v3/parsing/toml"
_ "github.com/tomwright/dasel/v3/parsing/xml"
Expand Down
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ go 1.23

require (
github.com/alecthomas/kong v1.2.1
github.com/google/go-cmp v0.5.9
github.com/google/go-cmp v0.6.0
github.com/pelletier/go-toml/v2 v2.2.2
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbles v0.20.0 // indirect
Expand All @@ -19,15 +22,20 @@ require (
github.com/charmbracelet/x/term v0.2.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/hashicorp/hcl/v2 v2.22.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/zclconf/go-cty v1.13.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.3.8 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.6.0 // indirect
)
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY=
github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q=
github.com/alecthomas/kong v1.2.1/go.mod h1:rKTSFhbdp3Ryefn8x5MOEprnRFQ7nlmMC01GKhehhBM=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
Expand All @@ -27,8 +33,12 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
Expand All @@ -41,6 +51,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
Expand All @@ -63,6 +75,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -71,6 +87,10 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
24 changes: 15 additions & 9 deletions model/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ type Value struct {
}

func (v *Value) String() string {
return v.string(0)
}

func indentStr(indent int) string {
return strings.Repeat(" ", indent)
}

func (v *Value) string(indent int) string {
switch v.Type() {
case TypeString:
val, err := v.StringValue()
Expand All @@ -78,15 +86,14 @@ func (v *Value) String() string {
}
return fmt.Sprintf("bool{%t}", val)
case TypeMap:
res := fmt.Sprintf("map[%d]{", len(v.Metadata))
res := fmt.Sprintf("{\n")
if err := v.RangeMap(func(k string, v *Value) error {
res += fmt.Sprintf("%s: %s, ", k, v.String())
res += fmt.Sprintf("%s%s: %s,\n", indentStr(indent+1), k, v.string(indent+1))
return nil
}); err != nil {
panic(err)
}
res = strings.TrimSuffix(res, ", ")
return res + "}"
return res + indentStr(indent) + "}"
case TypeSlice:
md := ""
if v.IsSpread() {
Expand All @@ -95,17 +102,16 @@ func (v *Value) String() string {
if v.IsBranch() {
md += "branch, "
}
res := fmt.Sprintf("array[%s]{", strings.TrimSuffix(md, ", "))
res := fmt.Sprintf("array[%s]{\n", strings.TrimSuffix(md, ", "))
if err := v.RangeSlice(func(k int, v *Value) error {
res += fmt.Sprintf("%d: %s, ", k, v.String())
res += fmt.Sprintf("%s%d: %s,\n", indentStr(indent+1), k, v.string(indent+1))
return nil
}); err != nil {
panic(err)
}
res = strings.TrimSuffix(res, ", ")
return res + "}"
return res + indentStr(indent) + "}"
case TypeNull:
return "null"
return indentStr(indent) + "null"
default:
return fmt.Sprintf("unknown[%s]", v.Interface())
}
Expand Down
19 changes: 19 additions & 0 deletions parsing/hcl/hcl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package hcl

import (
"github.com/tomwright/dasel/v3/parsing"
)

const (
// HCL represents the hcl2 file format.
HCL parsing.Format = "hcl"
)

var _ parsing.Reader = (*hclReader)(nil)

//var _ parsing.Writer = (*hclWriter)(nil)

func init() {
parsing.RegisterReader(HCL, newHCLReader)
parsing.RegisterWriter(HCL, newHCLWriter)
}
181 changes: 181 additions & 0 deletions parsing/hcl/reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package hcl

import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/tomwright/dasel/v3/model"
"github.com/tomwright/dasel/v3/parsing"
"github.com/zclconf/go-cty/cty"
)

func newHCLReader(options parsing.ReaderOptions) (parsing.Reader, error) {
return &hclReader{}, nil
}

type hclReader struct{}

// Read reads a value from a byte slice.
func (j *hclReader) Read(data []byte) (*model.Value, error) {
f, _ := hclsyntax.ParseConfig(data, "input", hcl.InitialPos)

body, ok := f.Body.(*hclsyntax.Body)
if !ok {
return nil, fmt.Errorf("failed to assert file body type")
}

return decodeHCLBody(body)
}

func decodeHCLBody(body *hclsyntax.Body) (*model.Value, error) {
res := model.NewMapValue()

for _, attr := range body.Attributes {
val, err := decodeHCLExpr(attr.Expr)
if err != nil {
return nil, fmt.Errorf("failed to decode attr %q: %w", attr.Name, err)
}

if err := res.SetMapKey(attr.Name, val); err != nil {
return nil, err
}
}

blockTypeIndexes := make(map[string]int)
blockValues := make([][]*model.Value, 0)
for _, block := range body.Blocks {
if _, ok := blockTypeIndexes[block.Type]; !ok {
blockValues = append(blockValues, make([]*model.Value, 0))
blockTypeIndexes[block.Type] = len(blockValues) - 1
}
res, err := decodeHCLBlock(block)
if err != nil {
return nil, fmt.Errorf("failed to decode block %q: %w", block.Type, err)
}
blockValues[blockTypeIndexes[block.Type]] = append(blockValues[blockTypeIndexes[block.Type]], res)
}

for t, index := range blockTypeIndexes {
blocks := blockValues[index]
switch len(blocks) {
case 0:
continue
case 1:
if err := res.SetMapKey(t, blocks[0]); err != nil {
return nil, err
}
default:
val := model.NewSliceValue()
for _, b := range blocks {
if err := val.Append(b); err != nil {
return nil, err
}
}
if err := res.SetMapKey(t, val); err != nil {
return nil, err
}
}
}

return res, nil
}

func decodeHCLBlock(block *hclsyntax.Block) (*model.Value, error) {
res, err := decodeHCLBody(block.Body)
if err != nil {
return nil, err
}

labels := model.NewSliceValue()
for _, l := range block.Labels {
if err := labels.Append(model.NewStringValue(l)); err != nil {
return nil, err
}
}

if err := res.SetMapKey("labels", labels); err != nil {
return nil, err
}

if err := res.SetMapKey("type", model.NewStringValue(block.Type)); err != nil {
return nil, err
}

return res, nil
}

func decodeHCLExpr(expr hcl.Expression) (*model.Value, error) {
source := cty.Value{}
_ = gohcl.DecodeExpression(expr, nil, &source)

return decodeCtyValue(source)
}

func decodeCtyValue(source cty.Value) (res *model.Value, err error) {
defer func() {
r := recover()
if r != nil {
err = fmt.Errorf("failed to decode: %v", r)
return
}
}()
if source.IsNull() {
return model.NewNullValue(), nil
}

sourceT := source.Type()
switch {
case sourceT.IsMapType():
return nil, fmt.Errorf("map type not implemented")
case sourceT.IsListType():
return nil, fmt.Errorf("list type not implemented")
case sourceT.IsCollectionType():
return nil, fmt.Errorf("collection type not implemented")
case sourceT.IsCapsuleType():
return nil, fmt.Errorf("capsule type not implemented")
case sourceT.IsTupleType():
res = model.NewSliceValue()
it := source.ElementIterator()
for it.Next() {
k, v := it.Element()
// We don't need the index as they should be in order.
// Just validates the key is correct.
_, _ = k.AsBigFloat().Float64()

val, err := decodeCtyValue(v)
if err != nil {
return nil, fmt.Errorf("failed to decode tuple value: %w", err)
}

if err := res.Append(val); err != nil {
return nil, err
}
}
return res, nil
case sourceT.IsObjectType():
return nil, fmt.Errorf("object type not implemented")
case sourceT.IsPrimitiveType():
switch sourceT {
case cty.String:
v := source.AsString()
return model.NewStringValue(v), nil
case cty.Bool:
v := source.True()
return model.NewBoolValue(v), nil
case cty.Number:
v := source.AsBigFloat()
f64, _ := v.Float64()
if v.IsInt() {
return model.NewIntValue(int64(f64)), nil
}
return model.NewFloatValue(f64), nil
default:
return nil, fmt.Errorf("unhandled primitive type %q", source.Type())
}
case sourceT.IsSetType():
return nil, fmt.Errorf("set type not implemented")
default:
return nil, fmt.Errorf("unhandled type: %s", sourceT.FriendlyName())
}
}
Loading

0 comments on commit a6074e8

Please sign in to comment.