From 5ab1b4760f785e6d5b735a76688fb806fee943ff Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Thu, 26 Sep 2024 12:53:13 -0700 Subject: [PATCH 1/3] Refactor Generator (#1772) --- internal/super/generate.go | 345 +----------------- internal/super/generator/contract_template.go | 119 ++++++ internal/super/generator/generator.go | 174 +++++++++ .../generator_test.go} | 126 +++---- internal/super/generator/script_template.go | 57 +++ .../templates/contract_counter.cdc.tmpl | 0 .../templates/contract_init.cdc.tmpl | 0 .../templates/contract_init_test.cdc.tmpl | 4 +- .../templates/script_counter.cdc.tmpl | 0 .../templates/script_init.cdc.tmpl | 0 .../templates/transaction_counter.cdc.tmpl | 4 +- .../templates/transaction_init.cdc.tmpl | 0 internal/super/generator/test_template.go | 64 ++++ .../super/generator/transaction_template.go | 61 ++++ internal/super/setup.go | 54 ++- 15 files changed, 570 insertions(+), 438 deletions(-) create mode 100644 internal/super/generator/contract_template.go create mode 100644 internal/super/generator/generator.go rename internal/super/{generate_test.go => generator/generator_test.go} (64%) create mode 100644 internal/super/generator/script_template.go rename internal/super/{ => generator}/templates/contract_counter.cdc.tmpl (100%) rename internal/super/{ => generator}/templates/contract_init.cdc.tmpl (100%) rename internal/super/{ => generator}/templates/contract_init_test.cdc.tmpl (68%) rename internal/super/{ => generator}/templates/script_counter.cdc.tmpl (100%) rename internal/super/{ => generator}/templates/script_init.cdc.tmpl (100%) rename internal/super/{ => generator}/templates/transaction_counter.cdc.tmpl (72%) rename internal/super/{ => generator}/templates/transaction_init.cdc.tmpl (100%) create mode 100644 internal/super/generator/test_template.go create mode 100644 internal/super/generator/transaction_template.go diff --git a/internal/super/generate.go b/internal/super/generate.go index dbc005be6..389c9ffcc 100644 --- a/internal/super/generate.go +++ b/internal/super/generate.go @@ -19,28 +19,15 @@ package super import ( - "bytes" - "embed" - "fmt" - "path/filepath" - "text/template" - - flowsdk "github.com/onflow/flow-go-sdk" - "github.com/onflow/flowkit/v2/config" - - "github.com/onflow/flow-cli/internal/util" - "github.com/onflow/flowkit/v2" - "github.com/onflow/flowkit/v2/output" - "github.com/onflow/flow-cli/internal/command" - "github.com/spf13/cobra" -) -//go:embed templates/*.tmpl -var templatesFS embed.FS + "github.com/onflow/flow-cli/internal/command" + "github.com/onflow/flow-cli/internal/super/generator" + "github.com/onflow/flow-cli/internal/util" +) type generateFlagsDef struct { Directory string `default:"" flag:"dir" info:"Directory to generate files in"` @@ -95,13 +82,6 @@ func init() { GenerateScriptCommand.AddToParent(GenerateCommand) } -const ( - DefaultCadenceDirectory = "cadence" - ContractType = "contract" - TransactionType = "transaction" - ScriptType = "script" -) - func generateContract( args []string, _ command.GlobalFlags, @@ -109,9 +89,9 @@ func generateContract( _ flowkit.Services, state *flowkit.State, ) (result command.Result, err error) { - generator := NewGenerator("", state, logger, false, true) - contract := Contract{Name: args[0], Account: ""} - err = generator.Create(TemplateMap{ContractType: []TemplateItem{contract}}) + g := generator.NewGenerator("", state, logger, false, true) + name := util.StripCDCExtension(args[0]) + err = g.Create(generator.ContractTemplate{Name: name, SkipTests: generateFlags.SkipTests, SaveState: true}) return nil, err } @@ -122,9 +102,9 @@ func generateTransaction( _ flowkit.Services, state *flowkit.State, ) (result command.Result, err error) { - generator := NewGenerator("", state, logger, false, true) - transaction := TransactionTemplate{Name: args[0]} - err = generator.Create(TemplateMap{TransactionType: []TemplateItem{transaction}}) + g := generator.NewGenerator("", state, logger, false, true) + name := util.StripCDCExtension(args[0]) + err = g.Create(generator.TransactionTemplate{Name: name}) return nil, err } @@ -135,307 +115,8 @@ func generateScript( _ flowkit.Services, state *flowkit.State, ) (result command.Result, err error) { - generator := NewGenerator("", state, logger, false, true) - script := ScriptTemplate{Name: args[0]} - err = generator.Create(TemplateMap{ScriptType: []TemplateItem{script}}) + g := generator.NewGenerator("", state, logger, false, true) + name := util.StripCDCExtension(args[0]) + err = g.Create(generator.ScriptTemplate{Name: name}) return nil, err } - -// TemplateItem is an interface for different template types -type TemplateItem interface { - GetName() string - GetTemplate() string - GetAccount() string - GetData() map[string]interface{} -} - -// Contract contains properties for contracts -type Contract struct { - Name string - Template string - Account string - Data map[string]interface{} -} - -// GetName returns the name of the contract -func (c Contract) GetName() string { - return c.Name -} - -// GetTemplate returns the template of the contract -func (c Contract) GetTemplate() string { - if c.Template == "" { - return "contract_init" - } - - return c.Template -} - -// GetAccount returns the account of the contract -func (c Contract) GetAccount() string { - return c.Account -} - -// GetData returns the data of the contract -func (c Contract) GetData() map[string]interface{} { - return c.Data -} - -// ScriptTemplate contains only a name property for scripts and transactions -type ScriptTemplate struct { - Name string - Template string - Data map[string]interface{} -} - -// GetName returns the name of the script or transaction -func (o ScriptTemplate) GetName() string { - return o.Name -} - -// GetTemplate returns an empty string for scripts and transactions -func (o ScriptTemplate) GetTemplate() string { - if o.Template == "" { - return "script_init" - } - - return o.Template -} - -// GetAccount returns an empty string for scripts and transactions -func (o ScriptTemplate) GetAccount() string { - return "" -} - -// GetData returns the data of the script or transaction -func (o ScriptTemplate) GetData() map[string]interface{} { - return o.Data -} - -// TransactionTemplate contains only a name property for scripts and transactions -type TransactionTemplate struct { - Name string - Template string - Data map[string]interface{} -} - -// GetName returns the name of the script or transaction -func (o TransactionTemplate) GetName() string { - return o.Name -} - -// GetTemplate returns an empty string for scripts and transactions -func (o TransactionTemplate) GetTemplate() string { - if o.Template == "" { - return "transaction_init" - } - - return o.Template -} - -// GetAccount returns an empty string for scripts and transactions -func (o TransactionTemplate) GetAccount() string { - return "" -} - -// GetData returns the data of the script or transaction -func (o TransactionTemplate) GetData() map[string]interface{} { - return o.Data -} - -// TemplateMap holds all templates with flexibility -type TemplateMap map[string][]TemplateItem - -type Generator struct { - directory string - state *flowkit.State - logger output.Logger - disableLogs bool - saveState bool -} - -func NewGenerator(directory string, state *flowkit.State, logger output.Logger, disableLogs, saveState bool) *Generator { - return &Generator{ - directory: directory, - state: state, - logger: logger, - disableLogs: disableLogs, - saveState: saveState, - } -} - -func (g *Generator) Create(typeNames TemplateMap) error { - for templateType, items := range typeNames { - for _, item := range items { - err := g.generate(templateType, item.GetTemplate(), item.GetName(), item.GetAccount(), item.GetData()) - if err != nil { - return err - } - } - } - return nil -} - -func (g *Generator) generate(templateType, templateName, name, account string, data map[string]interface{}) error { - - name = util.StripCDCExtension(name) - filename := util.AddCDCExtension(name) - - var fileToWrite string - var testFileToWrite string - var rootDir string - var basePath string - var testsBasePath = filepath.Join(DefaultCadenceDirectory, "tests") - var err error - - if g.directory != "" { - rootDir = g.directory - } - - templatePath := fmt.Sprintf("templates/%s.cdc.tmpl", templateName) - - switch templateType { - case ContractType: - basePath = filepath.Join(DefaultCadenceDirectory, "contracts") - fileData := map[string]interface{}{"Name": name} - for k, v := range data { - fileData[k] = v - } - fileToWrite, err = processTemplate(templatePath, fileData) - if err != nil { - return fmt.Errorf("error generating contract template: %w", err) - } - - testFileToWrite, err = processTemplate("templates/contract_init_test.cdc.tmpl", fileData) - if err != nil { - return fmt.Errorf("error generating contract test template: %w", err) - } - case ScriptType: - basePath = filepath.Join(DefaultCadenceDirectory, "scripts") - fileData := map[string]interface{}{} - for k, v := range data { - fileData[k] = v - } - fileToWrite, err = processTemplate(templatePath, fileData) - if err != nil { - return fmt.Errorf("error generating script template: %w", err) - } - case TransactionType: - basePath = filepath.Join(DefaultCadenceDirectory, "transactions") - fileData := map[string]interface{}{} - for k, v := range data { - fileData[k] = v - } - fileToWrite, err = processTemplate(templatePath, fileData) - if err != nil { - return fmt.Errorf("error generating transaction template: %w", err) - } - default: - return fmt.Errorf("invalid template type: %s", templateType) - } - - directoryWithBasePath := filepath.Join(rootDir, basePath, account) - filenameWithBasePath := filepath.Join(rootDir, basePath, account, filename) - relativeFilenameWithBasePath := filepath.Join(basePath, account, filename) - - // Check file existence - if _, err := g.state.ReaderWriter().ReadFile(filenameWithBasePath); err == nil { - return fmt.Errorf("file already exists: %s", filenameWithBasePath) - } - - // Ensure the directory exists - if err := g.state.ReaderWriter().MkdirAll(directoryWithBasePath, 0755); err != nil { - return fmt.Errorf("error creating directories: %w", err) - } - - // Write files - err = g.state.ReaderWriter().WriteFile(filenameWithBasePath, []byte(fileToWrite), 0644) - if err != nil { - return fmt.Errorf("error writing file: %w", err) - } - - if !g.disableLogs { - g.logger.Info(fmt.Sprintf("Generated new %s: %s at %s", templateType, name, filenameWithBasePath)) - } - - if generateFlags.SkipTests != true && templateType == ContractType { - testDirectoryWithBasePath := filepath.Join(rootDir, testsBasePath) - testFilenameWithBasePath := filepath.Join(rootDir, testsBasePath, util.AddCDCExtension(fmt.Sprintf("%s_test", name))) - - if _, err := g.state.ReaderWriter().ReadFile(testFilenameWithBasePath); err == nil { - return fmt.Errorf("file already exists: %s", testFilenameWithBasePath) - } - - if err := g.state.ReaderWriter().MkdirAll(testDirectoryWithBasePath, 0755); err != nil { - return fmt.Errorf("error creating test directory: %w", err) - } - - err := g.state.ReaderWriter().WriteFile(testFilenameWithBasePath, []byte(testFileToWrite), 0644) - if err != nil { - return fmt.Errorf("error writing test file: %w", err) - } - - if !g.disableLogs { - g.logger.Info(fmt.Sprintf("Generated new test file: %s at %s", name, testFilenameWithBasePath)) - } - } - - if templateType == ContractType { - err := g.updateContractsState(name, relativeFilenameWithBasePath) - if err != nil { - return err - } - } - - return nil -} - -func (g *Generator) updateContractsState(name, location string) error { - var aliases config.Aliases - - if generateFlags.SkipTests != true { - aliases = config.Aliases{{ - Network: config.TestingNetwork.Name, - Address: flowsdk.HexToAddress("0x0000000000000007"), - }} - } - - contract := config.Contract{ - Name: name, - Location: location, - Aliases: aliases, - } - - g.state.Contracts().AddOrUpdate(contract) - - if g.saveState { - err := g.state.SaveDefault() // TODO: Support adding a target project directory - if err != nil { - return fmt.Errorf("error saving to flow.json: %w", err) - } - } - - return nil -} - -// processTemplate reads a template file from the embedded filesystem and processes it with the provided data -// If you don't need to provide data, pass nil -func processTemplate(templatePath string, data map[string]interface{}) (string, error) { - templateData, err := templatesFS.ReadFile(templatePath) - if err != nil { - return "", fmt.Errorf("failed to read template file: %w", err) - } - - tmpl, err := template.New("template").Parse(string(templateData)) - if err != nil { - return "", fmt.Errorf("failed to parse template: %w", err) - } - - var executedTemplate bytes.Buffer - // Execute the template with the provided data or nil if no data is needed - if err = tmpl.Execute(&executedTemplate, data); err != nil { - return "", fmt.Errorf("failed to execute template: %w", err) - } - - return executedTemplate.String(), nil -} diff --git a/internal/super/generator/contract_template.go b/internal/super/generator/contract_template.go new file mode 100644 index 000000000..ae78f0263 --- /dev/null +++ b/internal/super/generator/contract_template.go @@ -0,0 +1,119 @@ +/* + * Flow CLI + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package generator + +import ( + "fmt" + "path/filepath" + + flowsdk "github.com/onflow/flow-go-sdk" + "github.com/onflow/flowkit/v2" + "github.com/onflow/flowkit/v2/config" + + "github.com/onflow/flow-cli/internal/util" +) + +const ( + DefaultContractDirectory = "contracts" + DefaultTestAddress = "0x0000000000000007" +) + +// Contract contains properties for contracts +type ContractTemplate struct { + Name string + Account string + TemplatePath string + Data map[string]interface{} + SkipTests bool + SaveState bool +} + +var _ TemplateItem = ContractTemplate{} +var _ TemplateItemWithStateUpdate = ContractTemplate{} +var _ TemplateItemWithChildren = ContractTemplate{} + +func (c ContractTemplate) GetType() string { + return "contract" +} + +func (c ContractTemplate) GetTemplatePath() string { + if c.TemplatePath == "" { + return "contract_init.cdc.tmpl" + } + + return c.TemplatePath +} + +func (c ContractTemplate) GetData() map[string]interface{} { + data := map[string]interface{}{ + "Name": c.Name, + } + + for k, v := range c.Data { + data[k] = v + } + return data +} + +func (c ContractTemplate) GetTargetPath() string { + return filepath.Join(DefaultCadenceDirectory, DefaultContractDirectory, c.Account, util.AddCDCExtension(c.Name)) +} + +func (c ContractTemplate) UpdateState(state *flowkit.State) error { + var aliases config.Aliases + + if c.SkipTests != true { + aliases = config.Aliases{{ + Network: config.TestingNetwork.Name, + Address: flowsdk.HexToAddress(DefaultTestAddress), + }} + } + + contract := config.Contract{ + Name: c.Name, + Location: c.GetTargetPath(), + Aliases: aliases, + } + + state.Contracts().AddOrUpdate(contract) + + if c.SaveState { + err := state.SaveDefault() // TODO: Support adding a target project directory + if err != nil { + return fmt.Errorf("error saving to flow.json: %w", err) + } + } + + return nil +} + +func (c ContractTemplate) GetChildren() []TemplateItem { + if c.SkipTests { + return []TemplateItem{} + } + + return []TemplateItem{ + TestTemplate{ + Name: fmt.Sprintf("%s_test", c.Name), + Data: map[string]interface{}{ + "ContractName": c.Name, + }, + }, + } +} diff --git a/internal/super/generator/generator.go b/internal/super/generator/generator.go new file mode 100644 index 000000000..aa05216f4 --- /dev/null +++ b/internal/super/generator/generator.go @@ -0,0 +1,174 @@ +/* + * Flow CLI + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package generator + +import ( + "bytes" + "embed" + "fmt" + "path/filepath" + "text/template" + + "github.com/onflow/flowkit/v2" + + "github.com/onflow/flowkit/v2/output" +) + +const ( + DefaultCadenceDirectory = "cadence" +) + +//go:embed templates/*.tmpl +var templatesFS embed.FS + +// TemplateItem is an interface for different template types +type TemplateItem interface { + GetType() string + GetTemplatePath() string + GetData() map[string]interface{} + GetTargetPath() string +} + +// TemplateItemWithStateUpdate is an interface for template items that need to update the Flowkit state/flow.json +type TemplateItemWithStateUpdate interface { + TemplateItem + UpdateState(state *flowkit.State) error +} + +// TemplateItemWithChildren is an interface for template items that have children +type TemplateItemWithChildren interface { + TemplateItem + GetChildren() []TemplateItem +} + +type Generator struct { + directory string + state *flowkit.State + logger output.Logger + disableLogs bool + saveState bool +} + +func NewGenerator( + directory string, + state *flowkit.State, + logger output.Logger, + disableLogs, + saveState bool, +) *Generator { + return &Generator{ + directory: directory, + state: state, + logger: logger, + disableLogs: disableLogs, + saveState: saveState, + } +} + +func (g *Generator) Create(items ...TemplateItem) error { + for _, item := range items { + err := g.generate(item) + if err != nil { + return err + } + + if itemWithChildren, ok := item.(TemplateItemWithChildren); ok { + err = g.Create(itemWithChildren.GetChildren()...) + if err != nil { + return err + } + } + } + + return nil +} + +func (g *Generator) generate(item TemplateItem) error { + rootDir := g.directory + + targetRelativeToRoot := item.GetTargetPath() + templatePath := item.GetTemplatePath() + data := item.GetData() + + fileData := map[string]interface{}{} + for k, v := range data { + fileData[k] = v + } + + outputContent, err := g.processTemplate(templatePath, fileData) + if err != nil { + return fmt.Errorf("error generating %s template: %w", item.GetType(), err) + } + + targetPath := filepath.Join(rootDir, targetRelativeToRoot) + targetDirectory := filepath.Dir(targetPath) + + // Check file existence + if _, err := g.state.ReaderWriter().ReadFile(targetPath); err == nil { + return fmt.Errorf("file already exists: %s", targetPath) + } + + // Ensure the directory exists + if err := g.state.ReaderWriter().MkdirAll(targetDirectory, 0755); err != nil { + return fmt.Errorf("error creating directories: %w", err) + } + + // Write files + err = g.state.ReaderWriter().WriteFile(targetPath, []byte(outputContent), 0644) + if err != nil { + return fmt.Errorf("error writing file: %w", err) + } + + if !g.disableLogs { + g.logger.Info(fmt.Sprintf("Generated new %s: %s", item.GetType(), targetPath)) + } + + // Call template state update function if it exists + if itemWithStateUpdate, ok := item.(TemplateItemWithStateUpdate); ok { + err = itemWithStateUpdate.UpdateState(g.state) + if err != nil { + return err + } + } + + return nil +} + +// processTemplate reads a template file from the embedded filesystem and processes it with the provided data +// If you don't need to provide data, pass nil +func (g *Generator) processTemplate(templatePath string, data map[string]interface{}) (string, error) { + resolvedPath := filepath.Join("templates", templatePath) + templateData, err := templatesFS.ReadFile(filepath.ToSlash(resolvedPath)) + if err != nil { + return "", fmt.Errorf("failed to read template file: %w", err) + } + + tmpl, err := template.New("template").Parse(string(templateData)) + if err != nil { + return "", fmt.Errorf("failed to parse template: %w", err) + } + + var executedTemplate bytes.Buffer + // Execute the template with the provided data or nil if no data is needed + if err = tmpl.Execute(&executedTemplate, data); err != nil { + return "", fmt.Errorf("failed to execute template: %w", err) + } + + return executedTemplate.String(), nil +} diff --git a/internal/super/generate_test.go b/internal/super/generator/generator_test.go similarity index 64% rename from internal/super/generate_test.go rename to internal/super/generator/generator_test.go index 3e368f07d..f4fca000c 100644 --- a/internal/super/generate_test.go +++ b/internal/super/generator/generator_test.go @@ -16,64 +16,45 @@ * limitations under the License. */ -package super +package generator import ( "fmt" "path/filepath" "testing" - "github.com/onflow/flow-cli/internal/util" - "github.com/stretchr/testify/assert" "github.com/onflow/flowkit/v2/output" + + "github.com/onflow/flow-cli/internal/util" ) func TestGenerateNewContract(t *testing.T) { logger := output.NewStdoutLogger(output.NoneLog) _, state, _ := util.TestMocks(t) - generator := NewGenerator("", state, logger, false, true) + g := NewGenerator("", state, logger, false, true) // Test contract generation - err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}}) + err := g.Create(ContractTemplate{Name: "TestContract"}) assert.NoError(t, err, "Failed to generate contract") fileContent, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/contracts/TestContract.cdc")) assert.NoError(t, err, "Failed to read generated file") assert.NotNil(t, fileContent) - testContent, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/tests/TestContract_test.cdc")) - assert.NoError(t, err, "Failed to read generated file") - assert.NotNil(t, testContent) - // Check content is correct expectedContent := `access(all) contract TestContract { init() {} }` - expectedTestContent := `import Test - -access(all) let account = Test.createAccount() - -access(all) fun testContract() { - let err = Test.deployContract( - name: "TestContract", - path: "../contracts/TestContract.cdc", - arguments: [], - ) - - Test.expect(err, Test.beNil()) -}` - assert.Equal(t, expectedContent, util.NormalizeLineEndings(string(fileContent))) - assert.Equal(t, expectedTestContent, util.NormalizeLineEndings(string(testContent))) // Test file already exists scenario generatorTwo := NewGenerator("", state, logger, false, true) - err = generatorTwo.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}}) + err = generatorTwo.Create(ContractTemplate{Name: "TestContract"}) assert.Error(t, err) expectedError := fmt.Sprintf("file already exists: %s", filepath.FromSlash("cadence/contracts/TestContract.cdc")) assert.Equal(t, expectedError, err.Error()) @@ -83,34 +64,25 @@ func TestGenerateContractWithAccount(t *testing.T) { logger := output.NewStdoutLogger(output.NoneLog) _, state, _ := util.TestMocks(t) - generator := NewGenerator("", state, logger, false, true) + g := NewGenerator("", state, logger, false, true) // Test contract generation - err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: "example-account"}}}) + err := g.Create(ContractTemplate{Name: "TestContract", Account: "example-account"}) assert.NoError(t, err, "Failed to generate contract") fileContent, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/contracts/example-account/TestContract.cdc")) assert.NoError(t, err, "Failed to read generated file") assert.NotNil(t, fileContent) - - testContent, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/tests/TestContract_test.cdc")) - assert.NoError(t, err, "Failed to read generated file") - assert.NotNil(t, testContent) } func TestGenerateNewContractSkipTests(t *testing.T) { logger := output.NewStdoutLogger(output.NoneLog) _, state, _ := util.TestMocks(t) - generateFlags.SkipTests = true - - generator := NewGenerator("", state, logger, false, true) - t.Cleanup(func() { - generateFlags.SkipTests = false - }) + g := NewGenerator("", state, logger, false, true) // Test contract generation - err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}}) + err := g.Create(ContractTemplate{Name: "TestContract", Account: "", SkipTests: true}) assert.NoError(t, err, "Failed to generate contract") fileContent, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/contracts/TestContract.cdc")) @@ -122,31 +94,13 @@ func TestGenerateNewContractSkipTests(t *testing.T) { assert.Nil(t, testContent) } -func TestGenerateNewContractWithCDCExtension(t *testing.T) { - logger := output.NewStdoutLogger(output.NoneLog) - _, state, _ := util.TestMocks(t) - - // Test contract generation - generator := NewGenerator("", state, logger, false, true) - err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "Tester.cdc", Account: ""}}}) - assert.NoError(t, err, "Failed to generate contract") - - fileContent, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/contracts/Tester.cdc")) - assert.NoError(t, err, "Failed to read generated file") - assert.NotNil(t, fileContent) - - testContent, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/tests/Tester_test.cdc")) - assert.NoError(t, err, "Failed to read generated file") - assert.NotNil(t, testContent) -} - func TestGenerateNewContractFileAlreadyExists(t *testing.T) { logger := output.NewStdoutLogger(output.NoneLog) _, state, _ := util.TestMocks(t) // Test contract generation - generator := NewGenerator("", state, logger, false, true) - err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}}) + g := NewGenerator("", state, logger, false, true) + err := g.Create(ContractTemplate{Name: "TestContract", Account: ""}) assert.NoError(t, err, "Failed to generate contract") //// Check if the file exists in the correct directory @@ -156,7 +110,7 @@ func TestGenerateNewContractFileAlreadyExists(t *testing.T) { // Test file already exists scenario generatorTwo := NewGenerator("", state, logger, false, true) - err = generatorTwo.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}}) + err = generatorTwo.Create(ContractTemplate{Name: "TestContract", Account: ""}) assert.Error(t, err) expectedError := fmt.Sprintf("file already exists: %s", filepath.FromSlash("cadence/contracts/TestContract.cdc")) assert.Equal(t, expectedError, err.Error()) @@ -166,8 +120,8 @@ func TestGenerateNewContractWithFileExtension(t *testing.T) { logger := output.NewStdoutLogger(output.NoneLog) _, state, _ := util.TestMocks(t) - generator := NewGenerator("", state, logger, false, true) - err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract.cdc", Account: ""}}}) + g := NewGenerator("", state, logger, false, true) + err := g.Create(ContractTemplate{Name: "TestContract.cdc", Account: ""}) assert.NoError(t, err, "Failed to generate contract") // Check file exists @@ -180,8 +134,8 @@ func TestGenerateNewScript(t *testing.T) { logger := output.NewStdoutLogger(output.NoneLog) _, state, _ := util.TestMocks(t) - generator := NewGenerator("", state, logger, false, true) - err := generator.Create(TemplateMap{"script": []TemplateItem{ScriptTemplate{Name: "TestScript"}}}) + g := NewGenerator("", state, logger, false, true) + err := g.Create(ScriptTemplate{Name: "TestScript"}) assert.NoError(t, err, "Failed to generate contract") content, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/scripts/TestScript.cdc")) @@ -199,8 +153,8 @@ func TestGenerateNewTransaction(t *testing.T) { logger := output.NewStdoutLogger(output.NoneLog) _, state, _ := util.TestMocks(t) - generator := NewGenerator("", state, logger, false, true) - err := generator.Create(TemplateMap{"transaction": []TemplateItem{TransactionTemplate{Name: "TestTransaction"}}}) + g := NewGenerator("", state, logger, false, true) + err := g.Create(TransactionTemplate{Name: "TestTransaction"}) assert.NoError(t, err, "Failed to generate contract") content, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/transactions/TestTransaction.cdc")) @@ -219,21 +173,51 @@ func TestGenerateNewWithDirFlag(t *testing.T) { logger := output.NewStdoutLogger(output.NoneLog) _, state, _ := util.TestMocks(t) - generator := NewGenerator("customDir", state, logger, false, true) - err := generator.Create(TemplateMap{"contract": []TemplateItem{Contract{Name: "TestContract", Account: ""}}}) + g := NewGenerator("customDir", state, logger, false, true) + err := g.Create(ContractTemplate{Name: "TestContract", Account: ""}) assert.NoError(t, err, "Failed to generate contract") content, err := state.ReaderWriter().ReadFile(filepath.FromSlash("customDir/cadence/contracts/TestContract.cdc")) assert.NoError(t, err, "Failed to read generated file") assert.NotNil(t, content) - testContent, err := state.ReaderWriter().ReadFile(filepath.FromSlash("customDir/cadence/tests/TestContract_test.cdc")) - assert.NoError(t, err, "Failed to read generated file") - assert.NotNil(t, testContent) - expectedContent := `access(all) contract TestContract { init() {} }` assert.Equal(t, expectedContent, util.NormalizeLineEndings(string(content))) } + +func TestGenerateTestTemplate(t *testing.T) { + logger := output.NewStdoutLogger(output.NoneLog) + _, state, _ := util.TestMocks(t) + + g := NewGenerator("", state, logger, false, true) + err := g.Create(TestTemplate{ + Name: "Foobar_test", + TemplatePath: "contract_init_test.cdc.tmpl", + Data: map[string]interface{}{ + "ContractName": "Foobar", + }}, + ) + assert.NoError(t, err, "Failed to generate file") + + content, err := state.ReaderWriter().ReadFile(filepath.FromSlash("cadence/tests/Foobar_test.cdc")) + assert.NoError(t, err, "Failed to read generated file") + assert.NotNil(t, content) + + expectedContent := `import Test + +access(all) let account = Test.createAccount() + +access(all) fun testContract() { + let err = Test.deployContract( + name: "Foobar", + path: "../contracts/Foobar.cdc", + arguments: [], + ) + + Test.expect(err, Test.beNil()) +}` + assert.Equal(t, expectedContent, util.NormalizeLineEndings(string(content))) +} diff --git a/internal/super/generator/script_template.go b/internal/super/generator/script_template.go new file mode 100644 index 000000000..ffde60371 --- /dev/null +++ b/internal/super/generator/script_template.go @@ -0,0 +1,57 @@ +/* + * Flow CLI + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package generator + +import ( + "path/filepath" + + "github.com/onflow/flow-cli/internal/util" +) + +const ( + DefaultScriptDirectory = "scripts" +) + +type ScriptTemplate struct { + Name string + TemplatePath string + Data map[string]interface{} +} + +var _ TemplateItem = ScriptTemplate{} + +func (o ScriptTemplate) GetType() string { + return "script" +} + +func (o ScriptTemplate) GetTemplatePath() string { + if o.TemplatePath == "" { + return "script_init.cdc.tmpl" + } + + return o.TemplatePath +} + +func (o ScriptTemplate) GetData() map[string]interface{} { + return o.Data +} + +func (o ScriptTemplate) GetTargetPath() string { + return filepath.Join(DefaultCadenceDirectory, DefaultScriptDirectory, util.AddCDCExtension(o.Name)) +} diff --git a/internal/super/templates/contract_counter.cdc.tmpl b/internal/super/generator/templates/contract_counter.cdc.tmpl similarity index 100% rename from internal/super/templates/contract_counter.cdc.tmpl rename to internal/super/generator/templates/contract_counter.cdc.tmpl diff --git a/internal/super/templates/contract_init.cdc.tmpl b/internal/super/generator/templates/contract_init.cdc.tmpl similarity index 100% rename from internal/super/templates/contract_init.cdc.tmpl rename to internal/super/generator/templates/contract_init.cdc.tmpl diff --git a/internal/super/templates/contract_init_test.cdc.tmpl b/internal/super/generator/templates/contract_init_test.cdc.tmpl similarity index 68% rename from internal/super/templates/contract_init_test.cdc.tmpl rename to internal/super/generator/templates/contract_init_test.cdc.tmpl index dc8cc25fb..89a8985d5 100644 --- a/internal/super/templates/contract_init_test.cdc.tmpl +++ b/internal/super/generator/templates/contract_init_test.cdc.tmpl @@ -4,8 +4,8 @@ access(all) let account = Test.createAccount() access(all) fun testContract() { let err = Test.deployContract( - name: "{{ .Name }}", - path: "../contracts/{{ .Name }}.cdc", + name: "{{ .ContractName }}", + path: "../contracts/{{ .ContractName }}.cdc", arguments: [], ) diff --git a/internal/super/templates/script_counter.cdc.tmpl b/internal/super/generator/templates/script_counter.cdc.tmpl similarity index 100% rename from internal/super/templates/script_counter.cdc.tmpl rename to internal/super/generator/templates/script_counter.cdc.tmpl diff --git a/internal/super/templates/script_init.cdc.tmpl b/internal/super/generator/templates/script_init.cdc.tmpl similarity index 100% rename from internal/super/templates/script_init.cdc.tmpl rename to internal/super/generator/templates/script_init.cdc.tmpl diff --git a/internal/super/templates/transaction_counter.cdc.tmpl b/internal/super/generator/templates/transaction_counter.cdc.tmpl similarity index 72% rename from internal/super/templates/transaction_counter.cdc.tmpl rename to internal/super/generator/templates/transaction_counter.cdc.tmpl index ee2e10a46..b120a843c 100644 --- a/internal/super/templates/transaction_counter.cdc.tmpl +++ b/internal/super/generator/templates/transaction_counter.cdc.tmpl @@ -1,8 +1,8 @@ import "{{ .ContractName }}" transaction { - prepare(signer: auth(Account) &Account) { - // Borrow a reference to the {{ .Name }} contract's public capability + prepare(signer: auth(BorrowValue) &Account) { + // Borrow a reference to the {{ .ContractName }} contract's public capability let counterRef = signer.borrow<&{{ .ContractName }}>(from: /storage/counter) ?? panic("Could not borrow reference to the counter") diff --git a/internal/super/templates/transaction_init.cdc.tmpl b/internal/super/generator/templates/transaction_init.cdc.tmpl similarity index 100% rename from internal/super/templates/transaction_init.cdc.tmpl rename to internal/super/generator/templates/transaction_init.cdc.tmpl diff --git a/internal/super/generator/test_template.go b/internal/super/generator/test_template.go new file mode 100644 index 000000000..de936ea27 --- /dev/null +++ b/internal/super/generator/test_template.go @@ -0,0 +1,64 @@ +/* + * Flow CLI + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package generator + +import ( + "path/filepath" + + "github.com/onflow/flow-cli/internal/util" +) + +const ( + DefaultTestDirectory = "tests" +) + +type TestTemplate struct { + Name string + TemplatePath string + Data map[string]interface{} +} + +var _ TemplateItem = TestTemplate{} + +func (o TestTemplate) GetType() string { + return "test" +} + +// GetName returns the name of the script or transaction +func (o TestTemplate) GetName() string { + return o.Name +} + +// GetTemplate returns an empty string for scripts and transactions +func (o TestTemplate) GetTemplatePath() string { + if o.TemplatePath == "" { + return "contract_init_test.cdc.tmpl" + } + + return o.TemplatePath +} + +// GetData returns the data of the script or transaction +func (o TestTemplate) GetData() map[string]interface{} { + return o.Data +} + +func (o TestTemplate) GetTargetPath() string { + return filepath.Join(DefaultCadenceDirectory, DefaultTestDirectory, util.AddCDCExtension(o.Name)) +} diff --git a/internal/super/generator/transaction_template.go b/internal/super/generator/transaction_template.go new file mode 100644 index 000000000..4accc1cdc --- /dev/null +++ b/internal/super/generator/transaction_template.go @@ -0,0 +1,61 @@ +/* + * Flow CLI + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package generator + +import ( + "path/filepath" + + "github.com/onflow/flow-cli/internal/util" +) + +const ( + DefaultTransactionDirectory = "transactions" +) + +// TransactionTemplate contains only a name property for scripts and transactions +type TransactionTemplate struct { + Name string + TemplatePath string + Data map[string]interface{} +} + +var _ TemplateItem = TransactionTemplate{} + +// GetName returns the name of the script or transaction +func (o TransactionTemplate) GetType() string { + return "transaction" +} + +// GetTemplate returns an empty string for scripts and transactions +func (o TransactionTemplate) GetTemplatePath() string { + if o.TemplatePath == "" { + return "transaction_init.cdc.tmpl" + } + + return o.TemplatePath +} + +// GetData returns the data of the script or transaction +func (o TransactionTemplate) GetData() map[string]interface{} { + return o.Data +} + +func (o TransactionTemplate) GetTargetPath() string { + return filepath.Join(DefaultCadenceDirectory, DefaultTransactionDirectory, util.AddCDCExtension(o.Name)) +} diff --git a/internal/super/setup.go b/internal/super/setup.go index acd84fd78..59477b8b1 100644 --- a/internal/super/setup.go +++ b/internal/super/setup.go @@ -25,9 +25,6 @@ import ( "os" "path/filepath" - "github.com/onflow/flow-cli/internal/dependencymanager" - "github.com/onflow/flow-cli/internal/util" - "github.com/spf13/afero" "github.com/onflow/flow-cli/internal/prompt" @@ -37,9 +34,11 @@ import ( "github.com/onflow/flowkit/v2" "github.com/onflow/flowkit/v2/output" - "github.com/onflow/flow-cli/internal/config" - "github.com/onflow/flow-cli/internal/command" + "github.com/onflow/flow-cli/internal/config" + "github.com/onflow/flow-cli/internal/dependencymanager" + "github.com/onflow/flow-cli/internal/super/generator" + "github.com/onflow/flow-cli/internal/util" ) type flagsSetup struct { @@ -202,37 +201,30 @@ func startInteractiveSetup( } // Generate standard cadence files - // cadence/contracts/DefaultContract.cdc - // cadence/scripts/DefaultScript.cdc - // cadence/transactions/DefaultTransaction.cdc - // cadence/tests/DefaultContract_test.cdc - - templates := TemplateMap{ - "contract": []TemplateItem{ - Contract{ - Name: "Counter", - Template: "contract_counter", - Account: "", - }, + // cadence/contracts/Counter.cdc + // cadence/scripts/GetCounter.cdc + // cadence/transactions/IncrementCounter.cdc + // cadence/tests/Counter_test.cdc + + templates := []generator.TemplateItem{ + generator.ContractTemplate{ + Name: "Counter", + TemplatePath: "contract_counter.cdc.tmpl", }, - "script": []TemplateItem{ - ScriptTemplate{ - Name: "GetCounter", - Template: "script_counter", - Data: map[string]interface{}{"ContractName": "Counter"}, - }, + generator.ScriptTemplate{ + Name: "GetCounter", + TemplatePath: "script_counter.cdc.tmpl", + Data: map[string]interface{}{"ContractName": "Counter"}, }, - "transaction": []TemplateItem{ - TransactionTemplate{ - Name: "IncrementCounter", - Template: "transaction_counter", - Data: map[string]interface{}{"ContractName": "Counter"}, - }, + generator.TransactionTemplate{ + Name: "IncrementCounter", + TemplatePath: "transaction_counter.cdc.tmpl", + Data: map[string]interface{}{"ContractName": "Counter"}, }, } - generator := NewGenerator(tempDir, state, logger, true, false) - err = generator.Create(templates) + g := generator.NewGenerator(tempDir, state, logger, true, false) + err = g.Create(templates...) if err != nil { return "", err } From 5408a986aba56b20a765fcbccefc25558ad59150 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Thu, 26 Sep 2024 13:53:10 -0700 Subject: [PATCH 2/3] Add command for generating test files (#1773) --- internal/super/generate.go | 25 +++++++++++++++++++ internal/super/generator/contract_template.go | 3 ++- .../generator/templates/empty_test.cdc.tmpl | 8 ++++++ internal/super/generator/test_template.go | 2 +- 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 internal/super/generator/templates/empty_test.cdc.tmpl diff --git a/internal/super/generate.go b/internal/super/generate.go index 389c9ffcc..9a3d2e9e0 100644 --- a/internal/super/generate.go +++ b/internal/super/generate.go @@ -76,10 +76,22 @@ var GenerateScriptCommand = &command.Command{ RunS: generateScript, } +var GenerateTestCommand = &command.Command{ + Cmd: &cobra.Command{ + Use: "test ", + Short: "Generate a Cadence test template", + Example: "flow generate test SomeTest", + Args: cobra.ExactArgs(1), + }, + Flags: &generateFlags, + RunS: generateTest, +} + func init() { GenerateContractCommand.AddToParent(GenerateCommand) GenerateTransactionCommand.AddToParent(GenerateCommand) GenerateScriptCommand.AddToParent(GenerateCommand) + GenerateTestCommand.AddToParent(GenerateCommand) } func generateContract( @@ -120,3 +132,16 @@ func generateScript( err = g.Create(generator.ScriptTemplate{Name: name}) return nil, err } + +func generateTest( + args []string, + _ command.GlobalFlags, + logger output.Logger, + _ flowkit.Services, + state *flowkit.State, +) (result command.Result, err error) { + g := generator.NewGenerator("", state, logger, false, true) + name := util.StripCDCExtension(args[0]) + err = g.Create(generator.TestTemplate{Name: name}) + return nil, err +} diff --git a/internal/super/generator/contract_template.go b/internal/super/generator/contract_template.go index ae78f0263..f8bbb5dd9 100644 --- a/internal/super/generator/contract_template.go +++ b/internal/super/generator/contract_template.go @@ -110,7 +110,8 @@ func (c ContractTemplate) GetChildren() []TemplateItem { return []TemplateItem{ TestTemplate{ - Name: fmt.Sprintf("%s_test", c.Name), + Name: fmt.Sprintf("%s_test", c.Name), + TemplatePath: "contract_init_test.cdc.tmpl", Data: map[string]interface{}{ "ContractName": c.Name, }, diff --git a/internal/super/generator/templates/empty_test.cdc.tmpl b/internal/super/generator/templates/empty_test.cdc.tmpl new file mode 100644 index 000000000..98ea2508a --- /dev/null +++ b/internal/super/generator/templates/empty_test.cdc.tmpl @@ -0,0 +1,8 @@ +import Test + +access(all) let account = Test.createAccount() + +access(all) fun testExample() { + // Test something + Test.expect(true, true) +} \ No newline at end of file diff --git a/internal/super/generator/test_template.go b/internal/super/generator/test_template.go index de936ea27..49e13e718 100644 --- a/internal/super/generator/test_template.go +++ b/internal/super/generator/test_template.go @@ -48,7 +48,7 @@ func (o TestTemplate) GetName() string { // GetTemplate returns an empty string for scripts and transactions func (o TestTemplate) GetTemplatePath() string { if o.TemplatePath == "" { - return "contract_init_test.cdc.tmpl" + return "empty_test.cdc.tmpl" } return o.TemplatePath From ee68926a571939d1debae52474c9606455ae77b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:14:17 +0000 Subject: [PATCH 3/3] Bump github.com/onflow/flow-evm-gateway from 0.36.3 to 0.36.4 Bumps [github.com/onflow/flow-evm-gateway](https://github.com/onflow/flow-evm-gateway) from 0.36.3 to 0.36.4. - [Release notes](https://github.com/onflow/flow-evm-gateway/releases) - [Commits](https://github.com/onflow/flow-evm-gateway/compare/v0.36.3...v0.36.4) --- updated-dependencies: - dependency-name: github.com/onflow/flow-evm-gateway dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2e11fe52b..d2df89d07 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/onflow/flixkit-go/v2 v2.0.0 github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1 github.com/onflow/flow-emulator v1.0.0 - github.com/onflow/flow-evm-gateway v0.36.3 + github.com/onflow/flow-evm-gateway v0.36.4 github.com/onflow/flow-go v0.37.10 github.com/onflow/flow-go-sdk v1.0.0-preview.56 github.com/onflow/flowkit/v2 v2.0.0 diff --git a/go.sum b/go.sum index 8aa4ffd73..a9ea56892 100644 --- a/go.sum +++ b/go.sum @@ -2178,8 +2178,8 @@ github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1 h1:FfhMBAb78p6VAWk github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1/go.mod h1:NgbMOYnMh0GN48VsNKZuiwK7uyk38Wyo8jN9+C9QE30= github.com/onflow/flow-emulator v1.0.0 h1:CCE9mFUYidb4YPQWFSBHzcBGggs5bXVqIh02wF2wRr0= github.com/onflow/flow-emulator v1.0.0/go.mod h1:sHbe9e1RG7Y6LA/dFyLEoBnKyjJ4iHeOdkXIobMjjrE= -github.com/onflow/flow-evm-gateway v0.36.3 h1:bk08W5bYTSKRmgR/TRwO9LbTp0c3rybH8QpZr8j/LuM= -github.com/onflow/flow-evm-gateway v0.36.3/go.mod h1:LWeu5hyXWU9mWPlKgeJOGcHRO67/hMtiyxtMFxRTZY4= +github.com/onflow/flow-evm-gateway v0.36.4 h1:AvPahikqfSN8SCMHn16ZkPz5Vvg60F1HkMHsFsQzkus= +github.com/onflow/flow-evm-gateway v0.36.4/go.mod h1:LWeu5hyXWU9mWPlKgeJOGcHRO67/hMtiyxtMFxRTZY4= github.com/onflow/flow-ft/lib/go/contracts v1.0.0 h1:mToacZ5NWqtlWwk/7RgIl/jeKB/Sy/tIXdw90yKHcV0= github.com/onflow/flow-ft/lib/go/contracts v1.0.0/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.0 h1:6cMS/lUJJ17HjKBfMO/eh0GGvnpElPgBXx7h5aoWJhs=