Skip to content

Commit

Permalink
Cleanup template globals
Browse files Browse the repository at this point in the history
  • Loading branch information
pgmitche committed Jan 30, 2024
1 parent 310cb35 commit 734e3f2
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 173 deletions.
26 changes: 11 additions & 15 deletions cmd/pkg/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cli
import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
Expand Down Expand Up @@ -51,7 +50,10 @@ var (

// Execute executes the root command.
func Execute() error {
return fmt.Errorf("execute: %w", rootCmd.Execute())
if err := rootCmd.Execute(); err != nil {
return fmt.Errorf("execute: %w", err)
}
return nil
}

func init() { //nolint:gochecknoinits
Expand Down Expand Up @@ -95,7 +97,7 @@ func dirRun(cmd *cobra.Command, _ []string) error { //nolint:gocyclo

d, _ := cmd.Flags().GetBool(displayFlag)

fs, err := ioutil.ReadDir(in)
fs, err := os.ReadDir(in)
if err != nil {
return fmt.Errorf("failed to read files for dir %s: %w", in, err)
}
Expand All @@ -112,13 +114,11 @@ func dirRun(cmd *cobra.Command, _ []string) error { //nolint:gocyclo
if ff.IsDir() {
continue
}

log.Printf("parsing copybook file %s", ff.Name())
f, err := os.Open(filepath.Join(in, ff.Name())) //nolint:gosec
if err != nil {
return fmt.Errorf("failed to open file %s: %w", ff.Name(), err)
}

if err := run(f, filepath.Join(out, ff.Name()), pkg, d); err != nil {
return err
}
Expand All @@ -132,45 +132,39 @@ func fileRun(cmd *cobra.Command, _ []string) error {
if err != nil {
return fmt.Errorf("failed to extract value for flag %s: %w", outFlag, err)
}

in, err := cmd.Flags().GetString(inFlag)
if err != nil {
return fmt.Errorf("failed to extract value for flag %s: %w", inFlag, err)
}

pkg, err := cmd.Flags().GetString(pkgFlag)
if err != nil {
return fmt.Errorf("failed to extract value for flag %s: %w", pkgFlag, err)
}

d, _ := cmd.Flags().GetBool(displayFlag)

log.Printf("parsing copybook file %s", in)
f, err := os.Open(in) //nolint:gosec
if err != nil {
return fmt.Errorf("failed to open file %s: %w", in, err)
}

return run(f, out, pkg, d)
}

func run(r io.Reader, output, pkg string, preview bool) error {
name := strings.TrimSuffix(output, filepath.Ext(output))
n := name[strings.LastIndex(name, "/")+1:]

c := copybook.New(n, pkg, template.Copybook())

b, err := ioutil.ReadAll(r)
b, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("failed to read input data: %w", err)
}

tree := lex.NewTree(lex.New(n, string(b)))
time.Sleep(time.Millisecond)
c.Root, err = tree.Parse()
if err != nil {
return fmt.Errorf("failed to parse copybook: %w", err)
}

// TODO: (pgmitche) if record in tree is struct but has no children,
// it should probably be ignored entirely
if preview {
Expand All @@ -187,6 +181,8 @@ func run(r io.Reader, output, pkg string, preview bool) error {
log.Fatalln(err)
}
}()

return fmt.Errorf("write to struct: %w", c.WriteToStruct(newFile))
if err := c.WriteToStruct(newFile); err != nil {
return fmt.Errorf("write to struct: %w", err)
}
return nil
}
235 changes: 115 additions & 120 deletions cmd/pkg/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,133 @@ import (
)

var (
// TODO: investigate https://github.com/masterminds/sprig for better
// templating?
//
// Global state for funcMap reference when templating
newStart = 1
newEnd = 1
savedStart = 1
savedEnd = 1
cursor = 1

structs = make([]string, 0)

special = regexp.MustCompile("[^a-zA-Z0-9]+")
)

func resetCounters() {
newStart = 1
newEnd = 1
savedStart = 1
savedEnd = 1
cursor = 1
type state struct {
newStart int
newEnd int
savedStart int
savedEnd int
cursor int
structs []string
}

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

func newTemplateState() *state {
return &state{
newStart: 1,
newEnd: 1,
savedStart: 1,
savedEnd: 1,
cursor: 1,
structs: make([]string, 0),
}
}

func (ts *state) getTemplateFuncs() template.FuncMap {
return template.FuncMap{
"goType": goType,
"picTag": newPicTag,
"sanitiseName": sanitiseName,
"indexComment": indexComment,
"isStruct": isStruct,
"buildStruct": buildStruct,
"getStructs": getStructs,
"sanitiseName": sanitiseName,
"picTag": ts.newPicTag,
"indexComment": ts.indexComment,
"buildStruct": ts.buildStruct,
"getStructs": ts.getStructs,
}
}

func getTemplate() *template.Template {
func (ts *state) newPicTag(length int, elemCount int) string {
size := ts.calculateSize(length, elemCount)
start := ts.newStart
end := start + (size - 1)

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

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

// 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
// so DUMMYGROUP3 now starts at 201, instead of 64
//
// type root struct {
// 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)
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 {
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
b := bytes.Buffer{}
if err := ts.getStructTemplate().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
return ""
}

func (ts *state) getStructs() []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

t, err := template.New("struct").
Funcs(ts.getTemplateFuncs()).
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 }}
{{- else }}
{{ sanitiseName $element.Name }} {{ goType $element }} {{ picTag $element.Length $element.Occurs}} {{ indexComment $element.Length $element.Occurs -}}
{{- end }}
{{- end }}
}`)
if err != nil {
panic(err)
}
return t
}

func (ts *state) parseTemplate() *template.Template {
return template.Must(
template.New("root").
Funcs(getTemplateFuncs()).
Funcs(ts.getTemplateFuncs()).
Parse(`
////////////////////////////////
// AUTOGENERATED FILE //
Expand Down Expand Up @@ -77,13 +165,6 @@ type {{ .Root.Name }} struct {
`))
}

func Copybook() *template.Template {
resetCounters()
structs = make([]string, 0)

return getTemplate()
}

// goType translates a type into a go type
func goType(l *lex.Record) string {
tag := ""
Expand All @@ -99,104 +180,18 @@ func goType(l *lex.Record) string {
case reflect.Struct:
tag = sanitiseName(l.Name)
default:
panic(fmt.Sprintf("unrecognized type %v", l.Typ))
panic(fmt.Errorf("unrecognized type %v", l.Typ))
}
if l.Occurs > 0 {
tag = fmt.Sprintf("[]%s", tag)
}
return tag
}

func newPicTag(length int, elemCount int) string {
// tag values
start := newStart
size := length
if elemCount > 0 {
size *= elemCount
}
end := start + (size - 1)

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

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

// 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
// so DUMMYGROUP3 now starts at 201, instead of 64
//
// type root struct {
// DUMMYGROUP1 DUMMYGROUP1 `pic:"63"` // start:1 end:63
// DUMMYGROUP3 DUMMYGROUP3 `pic:"201"` // start:190 end:390
// }
func indexComment(length int, elemCount int) string {
size := length
if elemCount > 0 {
size *= elemCount
}

s := cursor
e := s + size
cursor = e
return fmt.Sprintf(" // start:%d end:%d", s, e-1)
}

func sanitiseName(s string) string {
return special.ReplaceAllString(s, "")
}

func isStruct(r *lex.Record) bool {
return r.Typ == reflect.Struct
}

func getStructs() []string {
return structs
}

// FIXME: (pgmitche) if record is struct but has no children,
// it should probably be ignored entirely
func getStructTemplate() *template.Template {
newStart = 1
newEnd = 1
t, err := template.New("struct").
Funcs(getTemplateFuncs()).
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 }}
{{- else }}
{{ sanitiseName $element.Name }} {{ goType $element }} {{ picTag $element.Length $element.Occurs}} {{ indexComment $element.Length $element.Occurs -}}
{{- end }}
{{- end }}
}`)
if err != nil {
panic(err)
}

return t
}

// FIXME: (pgmitche) if record is struct but has no children,
// it should probably be ignored entirely
func buildStruct(r *lex.Record) string {
savedStart = newStart
savedEnd = newEnd
b := bytes.Buffer{}
if err := getStructTemplate().Execute(&b, r); err != nil {
panic(err)
}

structs = append(structs, b.String())
newStart = savedStart
newEnd = savedEnd
return ""
}
Loading

0 comments on commit 734e3f2

Please sign in to comment.