Skip to content

Commit 6d17dcb

Browse files
committed
0.2.3 iteration. CSV Output for []map, XML Output for html (needs fix for residual @ attrs compliant w/ tag > json spec)
1 parent f95ed3c commit 6d17dcb

File tree

7 files changed

+103
-37
lines changed

7 files changed

+103
-37
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ Note: these unsupported formats are on a roadmap for inclusion.
105105
| HCL | ✅ Supported | ✅ Supported |
106106
| TF | ✅ Supported | ✅ Supported |
107107
| GRON | ✅ Supported | ✅ Supported |
108-
| CSV | ✅ Supported | ❌ Not Supported |
108+
| CSV | ✅ Supported | Supported |
109109
| Protobuf | ❌ Not Supported | ❌ Not Supported |
110-
| HTML | ✅ Supported | ❌ Not Supported |
110+
| HTML | ✅ Supported | Supported |
111111
| TXT (newline)| ✅ Supported | ❌ Not Supported |
112112

113113

cli/qq.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func CreateRootCmd() *cobra.Command {
2323
encodings += t.Ext.String() + ", "
2424
}
2525
encodings = strings.TrimSuffix(encodings, ", ")
26-
v := "v0.2.2"
26+
v := "v0.2.3"
2727
desc := fmt.Sprintf("qq is a interoperable configuration format transcoder with jq querying ability powered by gojq. qq is multi modal, and can be used as a replacement for jq or be interacted with via a repl with autocomplete and realtime rendering preview for building queries. Supported formats include %s", encodings)
2828
cmd := &cobra.Command{
2929
Use: "qq [expression] [file] [flags] \n cat [file] | qq [expression] [flags] \n qq -I file",

codec/codec.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ var SupportedFileTypes = []Encoding{
8181
{TOML, toml.Unmarshal, toml.Marshal},
8282
{HCL, hcltf.Unmarshal, hcltf.Marshal},
8383
{TF, hcltf.Unmarshal, hcltf.Marshal},
84-
{CSV, sv.Unmarshal, jsn.Marshal},
84+
{CSV, sv.Unmarshal, sv.Marshal},
8585
{XML, xmll.Unmarshal, xmll.Marshal},
8686
{INI, inii.Unmarshal, inii.Marshal},
8787
{GRON, grn.Unmarshal, grn.Marshal},
88-
{HTML, htm.Unmarshal, jsn.Marshal},
88+
{HTML, htm.Unmarshal, xmll.Marshal},
8989
{LINE, lines.Unmarshal, jsn.Marshal},
9090
{TXT, lines.Unmarshal, jsn.Marshal},
9191
}

codec/csv/csv.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ package csv
33
import (
44
"bytes"
55
"encoding/csv"
6+
"errors"
67
"fmt"
78
"github.com/JFryy/qq/codec/util"
89
"github.com/goccy/go-json"
910
"io"
11+
"reflect"
12+
"slices"
1013
"strings"
1114
)
1215

@@ -37,6 +40,59 @@ func (c *Codec) detectDelimiter(input []byte) rune {
3740
return maxDelimiter
3841
}
3942

43+
func (c *Codec) Marshal(v interface{}) ([]byte, error) {
44+
var buf bytes.Buffer
45+
w := csv.NewWriter(&buf)
46+
47+
rv := reflect.ValueOf(v)
48+
if rv.Kind() != reflect.Slice {
49+
return nil, errors.New("input data must be a slice")
50+
}
51+
52+
if rv.Len() == 0 {
53+
return nil, errors.New("no data to write")
54+
}
55+
56+
firstElem := rv.Index(0).Interface()
57+
firstElemValue, ok := firstElem.(map[string]interface{})
58+
if !ok {
59+
return nil, errors.New("slice elements must be of type map[string]interface{}")
60+
}
61+
62+
var headers []string
63+
for key := range firstElemValue {
64+
headers = append(headers, key)
65+
}
66+
slices.Sort(headers)
67+
68+
if err := w.Write(headers); err != nil {
69+
return nil, fmt.Errorf("error writing CSV headers: %v", err)
70+
}
71+
72+
for i := 0; i < rv.Len(); i++ {
73+
recordMap := rv.Index(i).Interface().(map[string]interface{})
74+
row := make([]string, len(headers))
75+
for j, header := range headers {
76+
if value, ok := recordMap[header]; ok {
77+
row[j] = fmt.Sprintf("%v", value)
78+
} else {
79+
row[j] = ""
80+
}
81+
}
82+
if err := w.Write(row); err != nil {
83+
return nil, fmt.Errorf("error writing CSV record: %v", err)
84+
}
85+
}
86+
87+
w.Flush()
88+
89+
if err := w.Error(); err != nil {
90+
return nil, fmt.Errorf("error flushing CSV writer: %v", err)
91+
}
92+
93+
return buf.Bytes(), nil
94+
}
95+
4096
func (c *Codec) Unmarshal(input []byte, v interface{}) error {
4197
delimiter := c.detectDelimiter(input)
4298
r := csv.NewReader(strings.NewReader(string(input)))

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ require (
1818
github.com/spf13/cobra v1.8.1
1919
github.com/tmccombs/hcl2json v0.6.4
2020
github.com/zclconf/go-cty v1.15.0
21-
golang.org/x/net v0.29.0
21+
golang.org/x/net v0.30.0
2222
gopkg.in/ini.v1 v1.67.0
2323
)
2424

@@ -47,8 +47,8 @@ require (
4747
github.com/spf13/pflag v1.0.5 // indirect
4848
golang.org/x/mod v0.21.0 // indirect
4949
golang.org/x/sync v0.8.0 // indirect
50-
golang.org/x/sys v0.25.0 // indirect
51-
golang.org/x/text v0.18.0 // indirect
52-
golang.org/x/tools v0.25.0 // indirect
50+
golang.org/x/sys v0.26.0 // indirect
51+
golang.org/x/text v0.19.0 // indirect
52+
golang.org/x/tools v0.26.0 // indirect
5353
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
5454
)

go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,23 +97,23 @@ github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ
9797
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
9898
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
9999
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
100-
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
101-
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
100+
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
101+
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
102102
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
103103
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
104-
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
105-
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
104+
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
105+
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
106106
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
107107
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
108108
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
109109
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
110110
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
111-
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
112-
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
113-
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
114-
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
115-
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
116-
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
111+
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
112+
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
113+
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
114+
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
115+
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
116+
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
117117
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
118118
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
119119
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

tests/test.sh

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
#!/bin/bash
2+
13
set -e
24

3-
if [ -z $(which jq) ]; then
5+
if [ -z "$(which jq)" ]; then
46
echo "jq is not installed. Please install jq."
57
exit 1
68
fi
@@ -22,26 +24,30 @@ print() {
2224
esac
2325
}
2426

25-
26-
# ini has edge cases that prevent transcoding to other formats
2727
extensions=$(ls -1 tests/* | grep -Ev '.sh|ini')
2828
for i in ${extensions}; do
2929
echo "Testing $i"
30+
input=$(echo $i | cut -d. -f2)
31+
3032
for f in ${extensions}; do
3133
extension=$(echo $f | cut -d. -f2)
32-
input=$(echo $i | cut -d. -f2)
33-
# csv is not supported for toml and xml output
34-
case $input in
35-
csv)
36-
if [ $(echo $extension | grep -E 'toml') ]; then
37-
continue
38-
fi
39-
;;
40-
esac
34+
35+
if [[ "$input" == "csv" && "$extension" != "csv" ]]
36+
then
37+
print "yellow" "Skipping unsupported conversion from CSV to non-CSV compatible structure"
38+
continue
39+
fi
40+
41+
if [[ "$input" != csv && $extension == "csv" ]]
42+
then
43+
print "yellow" "Skipping unsupported conversion from CSV to non-CSV compatible structure"
44+
continue
45+
fi
46+
4147
print "" "============================================"
4248
print "" "Executing: cat $i | grep -v '#' | bin/qq -i $input -o $extension"
4349
print "" "============================================"
44-
cat $i | grep -v "#" | bin/qq -i $(echo $i | cut -d. -f2) -o $extension
50+
cat "$i" | grep -v "#" | bin/qq -i "$input" -o "$extension"
4551
print "green" "============================================"
4652
print "green" "Success."
4753
print "green" "============================================"
@@ -52,20 +58,24 @@ for i in ${extensions}; do
5258
print "" "============================================"
5359
print "yellow" "Testing case: qq $case $i"
5460
print "" "============================================"
55-
echo $test_cases
56-
cat $i | grep -v \# | bin/qq ${case} $i
61+
cat "$i" | grep -v \# | bin/qq "${case}" "$i"
5762
done
5863
done
5964

60-
# conversions to jq and back
6165
previous_ext="json"
6266
for file in ${extensions}; do
67+
if [[ $(echo -n $file | grep csv) ]]
68+
then
69+
continue
70+
fi
71+
print "" $file
6372
print "" "============================================"
6473
print "" "Executing: cat $file | jq . | bin/qq -o $previous_ext"
6574
print "" "============================================"
66-
bin/qq $file | jq . | bin/qq -o $previous_ext
75+
bin/qq "$file" | jq . | bin/qq -o "$previous_ext"
6776
print "green" "============================================"
6877
print "green" "Success."
6978
print "green" "============================================"
70-
previous_ext=$(echo $file | cut -d. -f2)
79+
previous_ext=$(echo "$file" | cut -d. -f2)
7180
done
81+

0 commit comments

Comments
 (0)