@@ 2,4 2,7 @@ module git.sr.ht/~mendelmaleh/csvgen
go 1.18
-require github.com/dave/jennifer v1.5.0 // indirect
+require (
+ github.com/dave/jennifer v1.5.0
+ github.com/mitchellh/hashstructure v1.1.0
+)
@@ 7,6 7,8 @@ github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBs
github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8=
github.com/dave/patsy v0.0.0-20210517141501-957256f50cba/go.mod h1:qfR88CgEGLoiqDaE+xxDCi5QA5v4vUoW0UCX2Nd5Tlc=
github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA=
+github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
+github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ 2,14 2,15 @@ package main
import (
"bufio"
- "fmt"
"go/token"
"log"
"os"
+ "sort"
"strings"
"unicode"
"github.com/dave/jennifer/jen"
+ "github.com/mitchellh/hashstructure"
)
func identifier(id string) string {
@@ 24,43 25,124 @@ func identifier(id string) string {
return strings.Join(words, "")
}
-func main() {
- // TODO: help flag
+func groupname(x []int) string {
+ r := make([]rune, len(x))
+ for i, v := range x {
+ r[i] = 'A' + rune(v)
+ }
+ return string(r)
+}
+
+func main() {
+ // headers
scanner := bufio.NewScanner(os.Stdin)
- var header string
- if scanner.Scan() {
- header = scanner.Text()
+ var headers [][]string
+ for scanner.Scan() {
+ headers = append(headers, strings.Split(scanner.Text(), ","))
}
if err := scanner.Err(); err != nil {
log.Fatalf("error reading header from stdin: %s")
}
- // TODO: sep flag, unquote fields
- fields := strings.Split(header, ",")
+ // fields to header indexes
+ fields := make(map[string][]int)
- /*
- fmt.Println(strings.Join(fields, "\n"))
- os.Exit(0)
- */
+ for i, header := range headers {
+ for _, field := range header {
+ fields[field] = append(fields[field], i)
+ }
+ }
- // TODO: struct name flag
- t := jen.Type().Id("Raw").StructFunc(func(g *jen.Group) {
- for _, v := range fields {
- id := identifier(v)
- if !token.IsIdentifier(id) {
- log.Fatalf("invalid identifier: %q", id)
- }
+ // header index group
+ var groups [][]int
- g.Id(id).String().Tag(map[string]string{"csv": v})
+ // header index group hash to fields
+ parts := make(map[uint64][]string)
+
+ for k, v := range fields {
+ hash, err := hashstructure.Hash(v, nil)
+ if err != nil {
+ log.Fatal(err)
}
+
+ if _, ok := parts[hash]; !ok {
+ groups = append(groups, v)
+ }
+
+ parts[hash] = append(parts[hash], k)
+ }
+
+ // sort groups
+ sort.Slice(groups, func(i, j int) bool {
+ li := len(groups[i])
+ lj := len(groups[j])
+
+ if li == lj {
+ for x := 0; x < li; x++ {
+ xi := groups[i][x]
+ xj := groups[j][x]
+
+ if xi != xj {
+ return xi < xj
+ }
+ }
+ }
+
+ // descending, biggest group (most common part) to least
+ return li > lj
})
- if err := t.Render(os.Stdout); err != nil {
- log.Fatal(err)
+ // header index to header groups
+ backrefs := make([][]int, len(headers))
+
+ for i, group := range groups {
+ if len(group) == 1 {
+ break
+ }
+
+ for _, v := range group {
+ backrefs[v] = append(backrefs[v], i)
+ }
+ }
+
+ // output
+ f := jen.NewFile("main")
+
+ for _, v := range groups {
+ hash, err := hashstructure.Hash(v, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sort.Strings(parts[hash])
+ // spew.Dump(parts[hash])
+
+ f.Type().Id(groupname(v)).StructFunc(func(g *jen.Group) {
+ // common parts
+ if len(v) == 1 {
+ for _, v := range backrefs[v[0]] {
+ g.Id(groupname(groups[v]))
+ }
+ g.Line()
+ }
+
+ // fields
+ for _, v := range parts[hash] {
+ id := identifier(v)
+ if !token.IsIdentifier(id) {
+ log.Fatalf("invalid identifier: %q", id)
+ }
+
+ g.Id(id).String().Tag(map[string]string{"csv": v})
+ }
+ }).Line()
+
}
- fmt.Println()
+ if err := f.Render(os.Stdout); err != nil {
+ log.Fatal(err)
+ }
}