Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
255 lines
7.3 KiB
Go
255 lines
7.3 KiB
Go
package message
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"io/fs"
|
|
"path/filepath"
|
|
|
|
"github.com/emicklei/proto"
|
|
"github.com/gobuffalo/genny/v2"
|
|
"github.com/gobuffalo/plush/v4"
|
|
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/protoanalysis/protoutil"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/xast"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/xgenny"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/templates/field/plushhelpers"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/templates/typed"
|
|
)
|
|
|
|
var (
|
|
//go:embed files/message/* files/message/**/*
|
|
fsMessage embed.FS
|
|
|
|
//go:embed files/simapp/* files/simapp/**/*
|
|
fsSimapp embed.FS
|
|
)
|
|
|
|
func Box(box fs.FS, opts *Options, g *genny.Generator) error {
|
|
if err := g.OnlyFS(box, nil, nil); err != nil {
|
|
return err
|
|
}
|
|
ctx := plush.NewContext()
|
|
ctx.Set("ModuleName", opts.ModuleName)
|
|
ctx.Set("ProtoVer", opts.ProtoVer)
|
|
ctx.Set("AppName", opts.AppName)
|
|
ctx.Set("MsgName", opts.MsgName)
|
|
ctx.Set("MsgDesc", opts.MsgDesc)
|
|
ctx.Set("MsgSigner", opts.MsgSigner)
|
|
ctx.Set("ModulePath", opts.ModulePath)
|
|
ctx.Set("Fields", opts.Fields)
|
|
ctx.Set("ResFields", opts.ResFields)
|
|
|
|
plushhelpers.ExtendPlushContext(ctx)
|
|
g.Transformer(xgenny.Transformer(ctx))
|
|
g.Transformer(genny.Replace("{{protoDir}}", opts.ProtoDir))
|
|
g.Transformer(genny.Replace("{{appName}}", opts.AppName))
|
|
g.Transformer(genny.Replace("{{moduleName}}", opts.ModuleName))
|
|
g.Transformer(genny.Replace("{{protoVer}}", opts.ProtoVer))
|
|
g.Transformer(genny.Replace("{{msgName}}", opts.MsgName.Snake))
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewGenerator returns the generator to scaffold a empty message in a module.
|
|
func NewGenerator(opts *Options) (*genny.Generator, error) {
|
|
g := genny.New()
|
|
|
|
g.RunFn(protoTxRPCModify(opts))
|
|
g.RunFn(protoTxMessageModify(opts))
|
|
g.RunFn(typesCodecModify(opts))
|
|
g.RunFn(clientCliTxModify(opts))
|
|
|
|
subMessage, err := fs.Sub(fsMessage, "files/message")
|
|
if err != nil {
|
|
return nil, errors.Errorf("fail to generate sub: %w", err)
|
|
}
|
|
|
|
if !opts.NoSimulation {
|
|
g.RunFn(moduleSimulationModify(opts))
|
|
subSimapp, err := fs.Sub(fsSimapp, "files/simapp")
|
|
if err != nil {
|
|
return nil, errors.Errorf("fail to generate sub: %w", err)
|
|
}
|
|
if err := Box(subSimapp, opts, g); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return g, Box(subMessage, opts, g)
|
|
}
|
|
|
|
// protoTxRPCModify modifies the tx.proto file to add the required RPCs and messages.
|
|
//
|
|
// What it expects:
|
|
// - A service named "Msg" to exist in the proto file, it appends the RPCs inside it.
|
|
func protoTxRPCModify(opts *Options) genny.RunFn {
|
|
return func(r *genny.Runner) error {
|
|
path := opts.ProtoFile("tx.proto")
|
|
f, err := r.Disk.Find(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
protoFile, err := protoutil.ParseProtoFile(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// = Add new rpc to Msg.
|
|
serviceMsg, err := protoutil.GetServiceByName(protoFile, "Msg")
|
|
if err != nil {
|
|
return errors.Errorf("failed while looking up service 'Msg' in %s: %w", path, err)
|
|
}
|
|
typenamePascal := opts.MsgName.PascalCase
|
|
protoutil.Append(
|
|
serviceMsg,
|
|
protoutil.NewRPC(
|
|
typenamePascal,
|
|
fmt.Sprintf("Msg%s", typenamePascal),
|
|
fmt.Sprintf("Msg%sResponse", typenamePascal),
|
|
),
|
|
)
|
|
|
|
newFile := genny.NewFileS(path, protoutil.Print(protoFile))
|
|
return r.File(newFile)
|
|
}
|
|
}
|
|
|
|
func protoTxMessageModify(opts *Options) genny.RunFn {
|
|
return func(r *genny.Runner) error {
|
|
path := opts.ProtoFile("tx.proto")
|
|
f, err := r.Disk.Find(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
protoFile, err := protoutil.ParseProtoFile(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Prepare the fields and create the messages.
|
|
creator := protoutil.NewField(opts.MsgSigner.Snake, "string", 1)
|
|
creator.Options = append(creator.Options, protoutil.NewOption("cosmos_proto.scalar", "cosmos.AddressString", protoutil.Custom())) // set the scalar annotation
|
|
creatorOpt := protoutil.NewOption(typed.MsgSignerOption, opts.MsgSigner.Snake)
|
|
msgFields := []*proto.NormalField{creator}
|
|
for i, field := range opts.Fields {
|
|
msgFields = append(msgFields, field.ToProtoField(i+2))
|
|
}
|
|
var resFields []*proto.NormalField
|
|
for i, field := range opts.ResFields {
|
|
resFields = append(resFields, field.ToProtoField(i+1))
|
|
}
|
|
|
|
typenamePascal := opts.MsgName.PascalCase
|
|
msg := protoutil.NewMessage(
|
|
"Msg"+typenamePascal,
|
|
protoutil.WithFields(msgFields...),
|
|
protoutil.WithMessageOptions(creatorOpt),
|
|
)
|
|
msgResp := protoutil.NewMessage("Msg"+typenamePascal+"Response", protoutil.WithFields(resFields...))
|
|
protoutil.Append(protoFile, msg, msgResp)
|
|
|
|
// Ensure custom types are imported
|
|
var protoImports []*proto.Import
|
|
for _, imp := range append(opts.ResFields.ProtoImports(), opts.Fields.ProtoImports()...) {
|
|
protoImports = append(protoImports, protoutil.NewImport(imp))
|
|
}
|
|
for _, f := range append(opts.ResFields.Custom(), opts.Fields.Custom()...) {
|
|
protoPath := fmt.Sprintf("%[1]v/%[2]v/%[3]v/%[4]v.proto", opts.AppName, opts.ModuleName, opts.ProtoVer, f)
|
|
protoImports = append(protoImports, protoutil.NewImport(protoPath))
|
|
}
|
|
if err = protoutil.AddImports(protoFile, true, protoImports...); err != nil {
|
|
return errors.Errorf("failed to add imports to %s: %w", path, err)
|
|
}
|
|
|
|
newFile := genny.NewFileS(path, protoutil.Print(protoFile))
|
|
return r.File(newFile)
|
|
}
|
|
}
|
|
|
|
func typesCodecModify(opts *Options) genny.RunFn {
|
|
return func(r *genny.Runner) error {
|
|
path := filepath.Join("x", opts.ModuleName, "types/codec.go")
|
|
f, err := r.Disk.Find(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Import
|
|
content, err := xast.AppendImports(f.String(), xast.WithNamedImport("sdk", "github.com/cosmos/cosmos-sdk/types"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
templateRegisterImplementations := `registrar.RegisterImplementations((*sdk.Msg)(nil),
|
|
&Msg%[1]v{},
|
|
)`
|
|
replacementRegisterImplementations := fmt.Sprintf(
|
|
templateRegisterImplementations,
|
|
opts.MsgName.PascalCase,
|
|
)
|
|
|
|
content, err = xast.ModifyFunction(
|
|
content,
|
|
"RegisterInterfaces",
|
|
xast.AppendFuncAtLine(replacementRegisterImplementations, 0),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newFile := genny.NewFileS(path, content)
|
|
return r.File(newFile)
|
|
}
|
|
}
|
|
|
|
func clientCliTxModify(opts *Options) genny.RunFn {
|
|
return func(r *genny.Runner) error {
|
|
path := filepath.Join("x", opts.ModuleName, "module/autocli.go")
|
|
f, err := r.Disk.Find(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
option := fmt.Sprintf(
|
|
`{
|
|
RpcMethod: "%[1]v",
|
|
Use: "%[2]v",
|
|
Short: "Send a %[3]v tx",
|
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{%[4]s},
|
|
}`,
|
|
opts.MsgName.PascalCase,
|
|
fmt.Sprintf("%s %s", opts.MsgName.Kebab, opts.Fields.CLIUsage()),
|
|
opts.MsgName.Original,
|
|
opts.Fields.ProtoFieldNameAutoCLI(),
|
|
)
|
|
content, err := typed.AppendAutoCLITxOptions(f.String(), option)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newFile := genny.NewFileS(path, content)
|
|
return r.File(newFile)
|
|
}
|
|
}
|
|
|
|
func moduleSimulationModify(opts *Options) genny.RunFn {
|
|
return func(r *genny.Runner) error {
|
|
path := filepath.Join("x", opts.ModuleName, "module/simulation.go")
|
|
f, err := r.Disk.Find(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
content, err := typed.ModuleSimulationMsgModify(
|
|
f.String(),
|
|
opts.ModulePath,
|
|
opts.ModuleName,
|
|
opts.MsgName,
|
|
opts.MsgSigner,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newFile := genny.NewFileS(path, content)
|
|
return r.File(newFile)
|
|
}
|
|
}
|