Gosingle
gosingle generate module level singleton based on the chosen structure or interface (map slice or array defined as type also supported).
Installation
go get github.com/alh1m1k/gosingle
Usage
The generator does not generate any initialization code, only declaration.
Use module level init() or any other way to properly initialize singleton.
By default, generator recursively inspect target structure or interface as well as it composition members.
It seek for exported field function, exported methods and then wrap it with module level proxy function.
Global singleton variable may be hidden via lower case variable name. Variable type (pointer or value) currently
defined by target type (for interfaces) or rcv type of the first exported method and not 100% accurate.
See todo and sources for more information.
Collision detector for function name and signature currently not 100% accurate (See todo and sources), it keept
the first collided function into output instead of dropping it all.
Composition member of field type may be excluded from inspect via tag `singl:"ignore"'
Filename for output file has template <package>/<target>_singleton.go
and can be changed via --suffix
or --filepath
.
File suffix _test.go
and _singleton.go
are excluded from analyze.
Path to source files a resolved via build.Default.Import(pkg, ".", build.FindOnly)
be careful with path and naming
All scalar type and all composite type are supported, as well as it's mixing and types decl are supported.
Generator will rename all _
or anonymous function parameter to p0, p1 ...pn
in order to generate valid function call
With the following structure in the executor.go file :
package queryExecutor
import (
"context"
"database/sql"
"errors"
)
type QueryExecutor struct {
One, Two string
}
// todo add opt contenxt param
func (receiver *QueryExecutor) Prepare(sqlStatement string) (context.Context, error) {
return nil, nil
}
func (receiver *QueryExecutor) Query(sqlStatement any, params ...any) (*sql.Rows, error) {
return nil, nil
}
func (receiver *QueryExecutor) Execute(sqlStatement any, params ...any) (sql.Result, error) {
return nil, nil
}
func (receiver *QueryExecutor) WithSql(ctx context.Context, sqlStatement string, params ...any) context.Context {
return nil
}
func (receiver *QueryExecutor) WithPrepare(ctx context.Context, sql *sql.Stmt) context.Context {
return ctx
}
func (receiver *QueryExecutor) prepareContext(ctx context.Context) (*sql.Stmt, []any, error) {
return nil, nil, nil
}
func (receiver *QueryExecutor) prepare(context context.Context, sqlStatement string) (*sql.Stmt, error) {
return nil, nil
}
func (receiver *QueryExecutor) run(sqlStatement any, done finalizer, params ...any) (any, error) {
return nil, nil
}
if you run :
$ gosingle github.com/me/queryExecutor QueryExecutor
you will have this output :
// Code generated by <git repo>. DO NOT EDIT.
package queryExecutor
import (
"context"
"database/sql"
"errors"
)
var Instance *QueryExecutor
func Prepare(sqlStatement string) (context.Context, error) {
return Instance.Prepare(sqlStatement)
}
func Query(sqlStatement any, params ...any) (*sql.Rows, error) {
return Instance.Query(sqlStatement, params...)
}
func Execute(sqlStatement any, params ...any) (sql.Result, error) {
return Instance.Execute(sqlStatement, params...)
}
func WithSql(ctx context.Context, sqlStatement string, params ...any) context.Context {
return Instance.WithSql(ctx, sqlStatement, params...)
}
func WithPrepare(ctx context.Context, sql *sql.Stmt) context.Context {
return Instance.WithPrepare(ctx, sql)
}
just add the -w flag (or use other file related flag such as --suffix, --filepath) to write it to queryExecutor_singleton.go.
Flags
--PKG package to walk to
--TARGET structure or interface that will be use as module singleton
--variable singleton instance (module variable) "Instance" by default
--comment code generated by <git repo>. DO NOT EDIT.
--suffix suffix of generated file "_singleton.go"
--filepath path for generated file --suffix will be ignored
--deep recursive deep
Status
Package in ready state. All basic functionality are implemented.
It has test coverage, but has lack of usage in real cases. So it can be broken, but not entirely :)
Generic currently not supported as it not trivial to convert struct[T]
to func[T]
Dependencies
The two main dependencies are :
- dave/jennifer: an awesome library for writing go code
- mow.cli : a cli command utility (probably will be dropped)
Inspired by
TODO
Checker must be improved to better handle function collision (check function signature instead of parameters count),
that is needed to distinguish between a collision with an interface and with another composition method, as behavior is quite different.
Singleton variable decl must be delayed until first method appear to determinate rcv type (pointer or value). Currently not 100% accurate, but works
in most cases.
- composition
- interface
- generics
- tags (ignore)
- test coverage & testing
- ambiguous function detector
- later variable decl
- drop custom cli