Skip to content

Commit

Permalink
tidying up outputs a bit, handling multiple structs per input
Browse files Browse the repository at this point in the history
  • Loading branch information
cneill committed Aug 18, 2023
1 parent c8fc696 commit 237be45
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 60 deletions.
58 changes: 41 additions & 17 deletions cmd/jsonstruct/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"log/slog"
"os"
"path"
"strings"

"github.com/cneill/jsonstruct"

Expand Down Expand Up @@ -75,7 +77,10 @@ func isStdin() bool {
}

func genStructs(ctx *cli.Context) error {
inputs := []*os.File{}
inputs, err := getInputs(ctx)
if err != nil {
return err
}

formatter, err := jsonstruct.NewFormatter(&jsonstruct.FormatterOptions{
SortFields: ctx.Bool("sort-fields"),
Expand All @@ -86,22 +91,11 @@ func genStructs(ctx *cli.Context) error {
return fmt.Errorf("failed to set up formatter: %w", err)
}

if isStdin() {
inputs = append(inputs, os.Stdin)
} else {
for _, fileName := range ctx.Args().Slice() {
file, err := os.Open(fileName)
if err != nil {
return fmt.Errorf("failed to open file %q: %w", fileName, err)
}

inputs = append(inputs, file)
}
}

for _, input := range inputs {
defer func() {
input.Close()

log.Debug("closed file", "file", input.Name())
}()

p := jsonstruct.NewParser(input, log)
Expand All @@ -111,21 +105,51 @@ func genStructs(ctx *cli.Context) error {
return fmt.Errorf("failed to parse input %q: %w", input.Name(), err)
}

if ctx.Bool("print-filename") {
fmt.Println(input.Name())
normalizedFileName := jsonstruct.GetGoName(strings.TrimSuffix(input.Name(), path.Ext(input.Name())))

for i := 0; i < len(results); i++ {
structName := fmt.Sprintf("%s%d", normalizedFileName, i+1)
results[i].SetName(structName)
}

if ctx.Bool("print-filenames") {
spacer := strings.Repeat("=", len(input.Name()))
fmt.Printf("%s\n%s\n%s\n", spacer, input.Name(), spacer)
}

result, err := formatter.FormatString(results...)
if err != nil {
return fmt.Errorf("failed to format struct(s) from %q: %w", input.Name(), err)
}

fmt.Printf("%s", result)
fmt.Printf("%s\n", result)
}

return nil
}

func getInputs(ctx *cli.Context) ([]*os.File, error) {
inputs := []*os.File{}

if isStdin() {
inputs = append(inputs, os.Stdin)

log.Debug("got JSON input from stdin")
} else {
for _, fileName := range ctx.Args().Slice() {
file, err := os.Open(fileName)
if err != nil {
return nil, fmt.Errorf("failed to open file %q: %w", fileName, err)
}
log.Debug("opened file to read JSON structs", "file", fileName)

inputs = append(inputs, file)
}
}

return inputs, nil
}

func main() {
if err := run(); err != nil {
log.Error("failed to execute", "err", err)
Expand Down
2 changes: 1 addition & 1 deletion fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (f Field) GetStruct() JSONStruct {
}

func (f Field) GetSliceStruct() JSONStruct {
result := JSONStruct{}.SetName(f.Name())
result := (&JSONStruct{}).SetName(f.Name())

anySlice, ok := f.rawValue.([]any)
if !ok {
Expand Down
24 changes: 17 additions & 7 deletions formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,40 @@ func NewFormatter(opts *FormatterOptions) (*Formatter, error) {
}

func (f *Formatter) FormatString(input ...JSONStruct) (string, error) {
result := ""
structStrings := []string{}

// TODO: handle arrays containing structs of the same kind differently
// TODO: handle inline structs

for _, js := range input {
if f.SortFields {
js.Fields.SortAlphabetically()
}

fieldStrings := f.fieldStrings(js.Fields...)
result += fmt.Sprintf("type %s struct {\n\t%s\n}", js.Name, strings.Join(fieldStrings, "\n\t"))
fieldStrings := strings.Join(f.fieldStrings(js.Fields...), "\n\t")
formatted := fmt.Sprintf("type %s struct {\n\t%s\n}", js.Name, fieldStrings)
structStrings = append(structStrings, formatted)

// we've already printed out all the relevant structs inline
if f.InlineStructs {
continue
}

// if we're not inlining structs, find all the fields of type struct and print their type definitions out too
// if we're not inlining structs, find all the fields of type struct / []struct and print their type definitions
// out too
for _, field := range js.Fields {
if !f.InlineStructs && field.IsStruct() || field.IsStructSlice() {
if field.IsStruct() || field.IsStructSlice() {
formatted, err := f.FormatString(field.GetStruct())
if err != nil {
return "", fmt.Errorf("failed to format child struct %q: %w", field.Name(), err)
}

result += fmt.Sprintf("\n\n%s", formatted)
structStrings = append(structStrings, formatted)
}
}
}

return result, nil
return strings.Join(structStrings, "\n\n"), nil
}

func (f *Formatter) fieldStrings(fields ...*Field) []string {
Expand Down
4 changes: 2 additions & 2 deletions jsonstruct.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ func New() JSONStruct {
}

// SetName sets the name to be used as a type for the JSONStruct.
func (j JSONStruct) SetName(name string) JSONStruct {
func (j *JSONStruct) SetName(name string) JSONStruct {
j.Name = name

return j
return *j
}

// AddFields appends Field objects to the JSONStruct.
Expand Down
66 changes: 33 additions & 33 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package jsonstruct

import (
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
Expand All @@ -28,51 +29,50 @@ func NewParser(input io.Reader, logger *slog.Logger) *Parser {
}

func (p *Parser) Start() (JSONStructs, error) {
first, err := p.peek()
if err != nil {
return nil, err
}

p.log.Debug("successfully read first token")

delim, ok := first.(json.Delim)
if !ok {
return nil, fmt.Errorf("expecting to start with a json.Delim, got %+v", first)
}

switch delim {
case '{':
js, err := p.parseObject()
if err != nil {
return nil, fmt.Errorf("failed to parse object: %w", err)
results := JSONStructs{}

for i := 0; ; i++ {
first, err := p.peek()
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, fmt.Errorf("failed to start parser: %w", err)
}

return JSONStructs{js}, nil
case '[':
jss, err := p.parseArray()
if err != nil {
return nil, fmt.Errorf("failed to parse array: %w", err)
}
p.log.Debug("successfully read first token")

if len(jss) == 0 {
return JSONStructs{}, nil
delim, ok := first.(json.Delim)
if !ok {
return nil, fmt.Errorf("expecting to start with a json.Delim, got %+v", first)
}

results := JSONStructs{}
p.log.Debug("successfully read a JSON delimiter", "delim", delim)

for i, jsRaw := range jss {
js, ok := jsRaw.(JSONStruct)
if !ok {
return nil, fmt.Errorf("array value at index %d was not a struct", i)
switch delim {
case '{':
js, err := p.parseObject()
if err != nil {
return nil, fmt.Errorf("failed to parse object: %w", err)
}

results = append(results, js)
}
case '[':
jsRaw, err := p.parseArray()
if err != nil {
return nil, fmt.Errorf("failed to parse array: %w", err)
}

return results, nil
structs, err := anySliceToJSONStructs(jsRaw)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON structs: %w", err)
}

results = append(results, structs...)
}
}

return nil, fmt.Errorf("invalid starting token: %+v", first)
// return nil, fmt.Errorf("invalid starting token: %+v", first)
return results, nil
}

func (p *Parser) next() error {
Expand Down

0 comments on commit 237be45

Please sign in to comment.