Skip to content

Commit

Permalink
Add template and copybook godoc, minor tidy
Browse files Browse the repository at this point in the history
  • Loading branch information
pgmitche committed Jan 30, 2024
1 parent 734e3f2 commit c2dbcb7
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 93 deletions.
58 changes: 32 additions & 26 deletions cmd/pkg/copybook/copybook.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,68 @@ import (
"github.com/foundatn-io/go-pic/pkg/lex"
)

// Copybook represents a copybook.
type Copybook struct {
Name string
Root *lex.Record
Package string
t *template.Template
Name string
Root *lex.Record
Package string
template *template.Template
}

// Record represents a record.
type Record struct {
Num int // Num is not necessarily required
Level int // Level is not necessarily required
Name string // Name is required
Picture reflect.Kind // Picture is required
Length int // Length is required
Occurs int // Occurs is required if present
Number int // Number is not necessarily required
Level int // Level is not necessarily required
Name string // Name is required
Type reflect.Kind // Type is required
Length int // Length is required
Occurrence int // Occurrence is required if present
}

func New(name, pkg string, t *template.Template) *Copybook {
// New creates a new copybook.
func New(name, pkg string, tmpl *template.Template) *Copybook {
return &Copybook{
Name: name,
Package: pkg,
t: t,
Name: name,
Package: pkg,
template: tmpl,
}
}

// Preview prints a preview of the copybook.
func (c *Copybook) Preview() {
log.Println("------ STRUCT PREVIEW -----")
treePrint(c.Root, 0)
printTree(c.Root, 0)
log.Println("------- END PREVIEW -------")
}

func treePrint(node *lex.Record, nest int) {
// printTree prints a tree representation of a record.
func printTree(node *lex.Record, nest int) {
if node.Typ == reflect.Struct {
indent := strings.Repeat(">", nest)
log.Printf("L%d: %s%s", nest, indent, node.Name)
nest++
for _, nn := range node.Children {
treePrint(nn, nest)
for _, nestedNode := range node.Children {
printTree(nestedNode, nest)
}
} else {
indent := strings.Repeat("-", nest)
log.Printf("L%d: %s%s", nest, indent, node.Name)
}
}

// WriteToStruct writes the copybook to a formatted struct.
func (c *Copybook) WriteToStruct(writer io.Writer) error {
var b bytes.Buffer
if err := c.t.Execute(&b, c); err != nil {
return fmt.Errorf("failed template copybook data: %w", err)
var buffer bytes.Buffer
if err := c.template.Execute(&buffer, c); err != nil {
return fmt.Errorf("failed to execute template: %w", err)
}
bb, err := format.Source(b.Bytes())
formatted, err := format.Source(buffer.Bytes())
if err != nil {
return fmt.Errorf("failed to gofmt templated copybook data: %w", err)
return fmt.Errorf("failed to format source: %w", err)
}
if _, err = writer.Write(bb); err != nil {
return fmt.Errorf("failed to write templated copybook data: %w", err)
if _, err = writer.Write(formatted); err != nil {
return fmt.Errorf("failed to write to writer: %w", err)
}
b.Reset()
buffer.Reset()
return nil
}
146 changes: 81 additions & 65 deletions cmd/pkg/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,62 +10,73 @@ import (
"github.com/foundatn-io/go-pic/pkg/lex"
)

var (
special = regexp.MustCompile("[^a-zA-Z0-9]+")
const (
stringTag = "string"
intTag = "int"
uintTag = "uint"
floatTag = "float64"
)

// specialCharsRegexp is a regular expression that matches any character that is not alphanumeric.
var specialCharsRegexp = regexp.MustCompile("[^a-zA-Z0-9]+")

// state holds the state of the template.
type state struct {
newStart int
newEnd int
savedStart int
savedEnd int
cursor int
structs []string
newStartPosition int
newEndPosition int
savedStartPosition int
savedEndPosition int
cursor int
structs []string
}

func Copybook() *template.Template {
ts := newTemplateState()
return ts.parseTemplate()
return ts.createParsedTemplate()
}

// newTemplateState creates a new state.
func newTemplateState() *state {
return &state{
newStart: 1,
newEnd: 1,
savedStart: 1,
savedEnd: 1,
cursor: 1,
structs: make([]string, 0),
newStartPosition: 1,
newEndPosition: 1,
savedStartPosition: 1,
savedEndPosition: 1,
cursor: 1,
structs: make([]string, 0),
}
}

func (ts *state) getTemplateFuncs() template.FuncMap {
// createTemplateFuncMap returns a map of functions that can be used in the template.
func (ts *state) createTemplateFuncMap() template.FuncMap {
return template.FuncMap{
"goType": goType,
"isStruct": isStruct,
"sanitiseName": sanitiseName,
"picTag": ts.newPicTag,
"indexComment": ts.indexComment,
"buildStruct": ts.buildStruct,
"getStructs": ts.getStructs,
"translateToGoType": translateToGoType,
"checkIfStruct": checkIfStruct,
"sanitiseName": sanitiseName,
"generatePicTag": ts.generatePicTag,
"generateIndexComment": ts.generateIndexComment,
"constructStruct": ts.constructStruct,
"retrieveStructs": ts.retrieveStructs,
}
}

func (ts *state) newPicTag(length int, elemCount int) string {
size := ts.calculateSize(length, elemCount)
start := ts.newStart
// generatePicTag generates a new PIC tag.
func (ts *state) generatePicTag(length int, elemCount int) string {
size := ts.calculateElementSize(length, elemCount)
start := ts.newStartPosition
end := start + (size - 1)

// manipulate state
ts.newEnd = end
ts.newStart = ts.newEnd + 1
ts.newEndPosition = end
ts.newStartPosition = ts.newEndPosition + 1
if elemCount > 0 {
return "`" + fmt.Sprintf("pic:\"%d,%d,%d\"", start, end, elemCount) + "`"
}

return "`" + fmt.Sprintf("pic:\"%d,%d\"", start, end) + "`"
}

// generateIndexComment generates a comment for the index.
// FIXME: (pgmitche) index comments are being over-calculated now,
// due to struct support.
// e.g. DUMMYGROUP1's length of 63 is being calculated 3x to 1+189
Expand All @@ -75,56 +86,58 @@ func (ts *state) newPicTag(length int, elemCount int) string {
// DUMMYGROUP1 DUMMYGROUP1 `pic:"63"` // start:1 end:63
// DUMMYGROUP3 DUMMYGROUP3 `pic:"201"` // start:190 end:390
// }
func (ts *state) indexComment(length int, elemCount int) string {
size := ts.calculateSize(length, elemCount)
func (ts *state) generateIndexComment(length int, elemCount int) string {
size := ts.calculateElementSize(length, elemCount)
s := ts.cursor
e := s + size
ts.cursor = e
return fmt.Sprintf(" // start:%d end:%d", s, e-1)
}

func (ts *state) calculateSize(length int, elemCount int) int {
// calculateElementSize calculates the size of the element.
func (ts *state) calculateElementSize(length int, elemCount int) int {
size := length
if elemCount > 0 {
size *= elemCount
}
return size
}

func (ts *state) buildStruct(r *lex.Record) string {
ts.savedStart = ts.newStart
ts.savedEnd = ts.newEnd
// constructStruct builds a struct from a record.
func (ts *state) constructStruct(r *lex.Record) string {
ts.savedStartPosition = ts.newStartPosition
ts.savedEndPosition = ts.newEndPosition
b := bytes.Buffer{}
if err := ts.getStructTemplate().Execute(&b, r); err != nil {
if err := ts.createStructTemplate().Execute(&b, r); err != nil {
panic(fmt.Errorf("execute struct templte: %w", err))
}
ts.structs = append(ts.structs, b.String())
ts.newStart = ts.savedStart
ts.newEnd = ts.savedEnd
ts.newStartPosition = ts.savedStartPosition
ts.newEndPosition = ts.savedEndPosition
return ""
}

func (ts *state) getStructs() []string {
// retrieveStructs returns the structs.
func (ts *state) retrieveStructs() []string {
return ts.structs
}

// FIXME: (pgmitche) if record is struct but has no children,
// it should probably be ignored entirely
func (ts *state) getStructTemplate() *template.Template {
ts.newStart = 1
ts.newEnd = 1
// createStructTemplate returns the template for a struct.
func (ts *state) createStructTemplate() *template.Template {
ts.newStartPosition = 1
ts.newEndPosition = 1

t, err := template.New("struct").
Funcs(ts.getTemplateFuncs()).
Funcs(ts.createTemplateFuncMap()).
Parse(`
// {{ sanitiseName .Name }} contains a representation of the nested group {{ .Name }}
type {{ sanitiseName .Name }} struct {
{{- range $element := .Children}}
{{- if isStruct $element }}
{{ sanitiseName $element.Name }} {{ goType $element -}} {{ picTag $element.Length $element.Occurs}}
{{- buildStruct $element }}
{{- if checkIfStruct $element }}
{{ sanitiseName $element.Name }} {{ translateToGoType $element -}} {{ generatePicTag $element.Length $element.Occurs}}
{{- constructStruct $element }}
{{- else }}
{{ sanitiseName $element.Name }} {{ goType $element }} {{ picTag $element.Length $element.Occurs}} {{ indexComment $element.Length $element.Occurs -}}
{{ sanitiseName $element.Name }} {{ translateToGoType $element }} {{ generatePicTag $element.Length $element.Occurs}} {{ generateIndexComment $element.Length $element.Occurs -}}
{{- end }}
{{- end }}
}`)
Expand All @@ -134,10 +147,11 @@ type {{ sanitiseName .Name }} struct {
return t
}

func (ts *state) parseTemplate() *template.Template {
// createParsedTemplate parses the template.
func (ts *state) createParsedTemplate() *template.Template {
return template.Must(
template.New("root").
Funcs(ts.getTemplateFuncs()).
Funcs(ts.createTemplateFuncMap()).
Parse(`
////////////////////////////////
// AUTOGENERATED FILE //
Expand All @@ -150,33 +164,33 @@ package {{ .Package }}
// {{ .Root.Name }} contains a representation of your provided Copybook
type {{ .Root.Name }} struct {
{{- range $element := .Root.Children}}
{{- if isStruct $element }}
{{- sanitiseName $element.Name }} {{ goType $element }} {{ picTag $element.Length $element.Occurs}}
{{- buildStruct $element }}
{{ else }}
{{ sanitiseName $element.Name }} {{ goType $element }} {{ picTag $element.Length $element.Occurs}}{{ indexComment $element.Length $element.Occurs -}}
{{- if checkIfStruct $element }}
{{- sanitiseName $element.Name }} {{ translateToGoType $element }} {{ generatePicTag $element.Length $element.Occurs}}
{{- constructStruct $element }}
{{ else }}
{{ sanitiseName $element.Name }} {{ translateToGoType $element }} {{ generatePicTag $element.Length $element.Occurs}}{{ generateIndexComment $element.Length $element.Occurs -}}
{{- end }}
{{- end }}
}
{{- range $elem := getStructs }}
{{- range $elem := retrieveStructs }}
{{ $elem }}
{{- end }}
`))
}

// goType translates a type into a go type
func goType(l *lex.Record) string {
tag := ""
// translateToGoType translates a type into a go type.
func translateToGoType(l *lex.Record) string {
var tag string
switch l.Typ {
case reflect.String:
tag = "string"
tag = stringTag
case reflect.Int:
tag = "int"
tag = intTag
case reflect.Uint:
tag = "uint"
tag = uintTag
case reflect.Float64:
tag = "float64"
tag = floatTag
case reflect.Struct:
tag = sanitiseName(l.Name)
default:
Expand All @@ -188,10 +202,12 @@ func goType(l *lex.Record) string {
return tag
}

// sanitiseName removes special characters from a string.
func sanitiseName(s string) string {
return special.ReplaceAllString(s, "")
return specialCharsRegexp.ReplaceAllString(s, "")
}

func isStruct(r *lex.Record) bool {
// checkIfStruct checks if a record is a struct.
func checkIfStruct(r *lex.Record) bool {
return r.Typ == reflect.Struct
}
2 changes: 0 additions & 2 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ func (e *UnmarshalTypeError) Error() string {
} else {
err = fmt.Errorf("pic: cannot unmarshal %s into Go value of type %s", e.Value, e.Type.String())
}

if e.Cause != nil {
return fmt.Sprintf("%s: %s", err.Error(), e.Cause.Error())
}

return err.Error()
}

0 comments on commit c2dbcb7

Please sign in to comment.