Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

convert powershell code generators to use go generate #55

Open
5 of 6 tasks
plastikfan opened this issue Jul 21, 2022 · 8 comments
Open
5 of 6 tasks

convert powershell code generators to use go generate #55

plastikfan opened this issue Jul 21, 2022 · 8 comments
Assignees
Labels
refactor Refactor code

Comments

@plastikfan
Copy link
Contributor

plastikfan commented Jul 21, 2022

See: go generate design

The powershell script is a bit of a fly in the ointment for this project as it requires any dev to know powershell pretty well in order to update the generators. These 2 skill sets are not particularly correlated, so code generation should be done in the Golang domain

This is not really that important, but would be a good task to work on to learn another aspect of Go development.

For a code generation reference, see: go-i18n codegen

Template resources:

Tasks:

  • create skeleton go generator #189
  • generate option-validator-auto #191
  • define a template file for each output, ie, there are currently 6 auto generated go files, eg option-validator-auto.go, so there should be a template file for each one of these.
  • define a template file for each snippet (this may conflict with the above task!)
  • implement check-sig #194
  • equalise the sha256 hashing between PowerShell and Go versions. There is no reasons why these hashes don't match if they use the same hashing algorithm. Make sure that the inputs to both are exactly the same. Currently, there may be a difference due to presence newline/carriage return, which differs between window/linux

To help in viewing template files (*.tmpl), set the language of these files to go, set in vscode settings (this sets the colour highlighting according to go and also sets the file icon type):

  "files.associations": {
    "**/*.tmpl": "go"
  }
@plastikfan plastikfan added the refactor Refactor code label Jul 21, 2022
@plastikfan plastikfan self-assigned this Jul 21, 2022
@plastikfan
Copy link
Contributor Author

plastikfan commented Sep 7, 2023

@plastikfan
Copy link
Contributor Author

Use the generate command in a comment. When you run go generate, it will search all the source files that contains the go generate command and run them

//go:generate <then some executable and args ....>

You can create aliases

//go:generate -command foo echo msg:

creates an alias named foo which runs echo on the parameter msg, eg

//go:generate foo hello

go generate ./...

msg: hello-world

@plastikfan
Copy link
Contributor Author

plastikfan commented Sep 7, 2023

I think we'll also need to combine templates with generate, ie the existing powershell script generate-options-validators.ps1 will be used as a reference of how to define the templates. There should also be some config too (not sure yet what it'll contain). Once we have the templates, we will also define a go based generator that will use the templates and generate the code.

Actually, one such piece of config data will be the output directory of where to generate the output. Initially, we will definitely need this to check what we generate matches the existing generated code auto files.

The go program we create, will be the program we reference from the go:generate comment statement.

@plastikfan
Copy link
Contributor Author

plastikfan commented Sep 7, 2023

Can use to generate mocks (gomock)

Some examples of code generation:

  • ffjson (json marshaller): can use for generating marshalling code to improve performance as a replacement for reflection. There are many other tools that do this kind of thing too.
  • sqlc: generates go code from sqlc (or t-sql).
  • protoc: automatic generation of proxy/stub code for protobuf in multiple languages
  • swagger/openapi: similar to protoc

@plastikfan
Copy link
Contributor Author

Actually, to implement this, we need to define a new project whose purpose is to generate the boilerplate source code and the accompanying tests. This separate program is then installed locally. We then add appropriate go:generate comments to invoke our program. So really, go:generate doesnt do that much for us other than to provide the plumbing to stich the parts together, there is no magic here! Look at stringer for an example.

@plastikfan
Copy link
Contributor Author

plastikfan commented Sep 8, 2023

a template is essentially map[string]*Template.

🎈 To define a template:

{{ template "footer }}

<p>
<div>my blog</div>
	{{ template "copyright" }}
</p>

{{ define "copyright" }}
Copyright 2020. All RIghts Reserved
{{ end }}

🎯 so when you execute {{ template "footer" }}

you are performing a map[string]*Template for "footer"

map[string]*Template{
	"hello.html": &Template{
		name: "hello.html"
		Tree: /* template body*/
	},
	"footer": &Template{
		name: "footer"
		Tree: /* template body*/
	},
	copyright": &Template{
		name: "copyright"
		Tree: /* template body*/
	},
}

🎈 There are 6 keywords: define, template, block, if/else, with, range

🎯 Variables

each template has access to 1 variable known as the .
the . variable is whatever you pass into Execute/ExecuteTemplate

  • when . is a struct
struct{
	Params: Params{
		Author: Author{
			LastName: "John",
			LastName: "Doe",
		},
	},
}
{{ . }}
{{ .Params.Author.LastName }}

the . refers to the struct above

  • when dot is:
map[string]interface{}{
	"Params": map[string]interface{}{
		"Author": map[string]interface{}{
			"FirstName": "John",
			"LastName": "Doe",
		}
	}
}
{{ . }}
{{ .Params.Author.LastName }}

the . refers to the map above ie, if you pass in the map into the Execute function

🧿 this all works via reflection

You can define your own variable but there are all relative to .

{{ define "greet" }}
	{{ $firstName := .Params.User.FirstName }} // declaration & assignment
	<p>hello, name is {{ $firstName }}</p>
	{{ firstName = "Joe" }} // reassignment
	<p>actually, my name is {{ $firstName }}</p>
{{ end }}

scope of a variable extends to the matching {{ end }} of the current block

🎈 dot and dollar

. refers to the current template variable, but so does $

--> the difference is that the dot changes depending on context

the dot changes inside {{ with }} and {{ range }} bocks

the dollar always points to the toplevel template variable, it never changes, where as the
dot starts off at the top level, but can change

🎈 with

a bit like {{ if }}

usage:

{{ with }} {{ end }}

if is falsy, the block is not executed
the dot '.' variable is set to inside the block

🎈 range

loops over a variable

usage:

{{ range <var> }} {{ else }} {{ end }}

<var> must be array, slice, map or channel

if there are 0 element, the else block is executed

you can also specify an index variable in the range statement (assuming you pass in a slice/array/chan)
(for map, $i would be the key):

{{ range $i, $user := .Users }}
... template content

{{ else }}
... template content
{{ end }}

🎯 This allows our template to generate dynamic content

🎈 functions methods and pipes

example functions:

  • and/or/not/index/len

  • and
    {{ if and }} {{ end }}

  • len
    {{ len }} {{ end }}

(where var is string|slice|array|map|channel)

  • index
{{ index <slice> <num> ... }}
{{ index <map> <key> ... }}

🎈 templates can call user defined functions .Funcs(map[string]interface{})

template.
	New("t").
	Funcs(map[string]interface{}){
		"greet": func(name string) string { return "hello" + name}
	}).
	Parse(`{{ greet "bob" }}`)

funcs take either of these forms:

func() string
func(s string) string
func(args ...interface{}) interface{}

you can also return an error, when error occurs template engine will stop executing

func() (string, error)
func(s string) (string, error)
func(args ...interface{}) (string, interface{}) // !! this looks fishy, should the return be?: (error, interface{})

🎈 templates can call methods

type User struct { greeting string }

func (u User) Greet(name string) string {
	return u.greeting + " " + name
}

t, _ := template.New("t"),Parse(`{{ .User.Greet "bob" }}`)
t.Execute(os.Stdout, map[string]interface{}){
	"User": User{greeting: "hello"},
}

if a member of a struct is variable that is a func, then the template
can't invoke it directory. It has to be invoked using the 'call' keyword

---> the results of 1 function can be piped into another using |

@plastikfan
Copy link
Contributor Author

Another potential useful reference: Go Templates - Simple and Powerful

@plastikfan
Copy link
Contributor Author

to print a value that appears inside quotes, you can use printf go template function with %q format specifier. The %q, is a placeholder of a value that will be quoted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactor Refactor code
Projects
None yet
Development

No branches or pull requests

1 participant