Repository: onflow/flow-cli (go)

Issue:
Disable logs in generate for setup
Logs appear inside of the `generate` logic saying where they were created. There should be a way to disable logs in generate since the interactive setup uses its own logging.

Pull request:
Refactor generate logic, add disable logs, and disable from setup
Closes #1554 

______

For contributor use:

- [ ] Targeted PR against `master` branch
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [ ] Updated relevant documentation
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels


Patch:
diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go
index 7efad4f9..261a5535 100644
--- a/internal/dependencymanager/install.go
+++ b/internal/dependencymanager/install.go
@@ -20,6 +20,7 @@ package dependencymanager
 
 import (
 	"fmt"
+
 	"github.com/onflow/flow-cli/internal/util"
 
 	"github.com/spf13/cobra"
diff --git a/internal/super/generate.go b/internal/super/generate.go
index 2dee9177..11c759b0 100644
--- a/internal/super/generate.go
+++ b/internal/super/generate.go
@@ -23,13 +23,13 @@ import (
 	"embed"
 	"fmt"
 	"path/filepath"
-	"strings"
 	"text/template"
 
 	flowsdk "github.com/onflow/flow-go-sdk"
-
 	"github.com/onflow/flowkit/config"
 
+	"github.com/onflow/flow-cli/internal/util"
+
 	"github.com/onflow/flowkit"
 
 	"github.com/onflow/flowkit/output"
@@ -95,6 +95,13 @@ func init() {
 	GenerateScriptCommand.AddToParent(GenerateCommand)
 }
 
+const (
+	DefaultCadenceDirectory = "cadence"
+	ContractType            = "contract"
+	TransactionType         = "transaction"
+	ScriptType              = "script"
+)
+
 func generateContract(
 	args []string,
 	_ command.GlobalFlags,
@@ -102,7 +109,9 @@ func generateContract(
 	_ flowkit.Services,
 	state *flowkit.State,
 ) (result command.Result, err error) {
-	return generateNew(args, "contract", generateFlags.Directory, logger, state)
+	generator := NewGenerator(DefaultCadenceDirectory, state, logger, false)
+	err = generator.Create(TemplateMap{ContractType: []string{args[0]}})
+	return nil, err
 }
 
 func generateTransaction(
@@ -112,7 +121,9 @@ func generateTransaction(
 	_ flowkit.Services,
 	state *flowkit.State,
 ) (result command.Result, err error) {
-	return generateNew(args, "transaction", generateFlags.Directory, logger, state)
+	generator := NewGenerator(DefaultCadenceDirectory, state, logger, false)
+	err = generator.Create(TemplateMap{TransactionType: []string{args[0]}})
+	return nil, err
 }
 
 func generateScript(
@@ -122,157 +133,185 @@ func generateScript(
 	_ flowkit.Services,
 	state *flowkit.State,
 ) (result command.Result, err error) {
-	return generateNew(args, "script", generateFlags.Directory, logger, state)
+	generator := NewGenerator(DefaultCadenceDirectory, state, logger, false)
+	err = generator.Create(TemplateMap{ScriptType: []string{args[0]}})
+	return nil, err
 }
 
-func addCDCExtension(name string) string {
-	if strings.HasSuffix(name, ".cdc") {
-		return name
-	}
-	return fmt.Sprintf("%s.cdc", name)
-}
+// TemplateMap defines a map of template types to their specific names
+type TemplateMap map[string][]string
 
-func stripCDCExtension(name string) string {
-	return strings.TrimSuffix(name, filepath.Ext(name))
+type Generator struct {
+	directory   string
+	state       *flowkit.State
+	logger      output.Logger
+	disableLogs bool
 }
 
-// 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)
+func NewGenerator(directory string, state *flowkit.State, logger output.Logger, disableLogs bool) *Generator {
+	return &Generator{
+		directory:   directory,
+		state:       state,
+		logger:      logger,
+		disableLogs: disableLogs,
 	}
+}
 
-	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)
+func (g *Generator) Create(typeNames TemplateMap) error {
+	for templateType, names := range typeNames {
+		for _, name := range names {
+			err := g.generate(templateType, name)
+			if err != nil {
+				return err
+			}
+		}
 	}
-
-	return executedTemplate.String(), nil
+	return nil
 }
 
-func generateNew(
-	args []string,
-	templateType string,
-	directory string,
-	logger output.Logger,
-	state *flowkit.State,
-) (result command.Result, err error) {
-	if len(args) < 1 {
-		return nil, fmt.Errorf("invalid number of arguments")
-	}
+func (g *Generator) generate(templateType, name string) error {
 
-	name := stripCDCExtension(args[0])
-	filename := addCDCExtension(name)
+	name = util.StripCDCExtension(name)
+	filename := util.AddCDCExtension(name)
 
 	var fileToWrite string
 	var testFileToWrite string
-	var rootDir = "cadence"
+	var rootDir = DefaultCadenceDirectory
 	var basePath string
 	var testsBasePath = "tests"
+	var err error
 
-	if directory != "" {
-		rootDir = directory
+	if g.directory != "" {
+		rootDir = g.directory
 	}
 
 	switch templateType {
-	case "contract":
+	case ContractType:
 		basePath = "contracts"
 		nameData := map[string]interface{}{"Name": name}
 		fileToWrite, err = processTemplate("templates/contract_init.cdc.tmpl", nameData)
 		if err != nil {
-			return nil, fmt.Errorf("error generating contract template: %w", err)
+			return fmt.Errorf("error generating contract template: %w", err)
 		}
 
 		testFileToWrite, err = processTemplate("templates/contract_init_test.cdc.tmpl", nameData)
 		if err != nil {
-			return nil, fmt.Errorf("error generating contract test template: %w", err)
+			return fmt.Errorf("error generating contract test template: %w", err)
 		}
-	case "script":
+	case ScriptType:
 		basePath = "scripts"
 		fileToWrite, err = processTemplate("templates/script_init.cdc.tmpl", nil)
 		if err != nil {
-			return nil, fmt.Errorf("error generating script template: %w", err)
+			return fmt.Errorf("error generating script template: %w", err)
 		}
-	case "transaction":
+	case TransactionType:
 		basePath = "transactions"
 		fileToWrite, err = processTemplate("templates/transaction_init.cdc.tmpl", nil)
 		if err != nil {
-			return nil, fmt.Errorf("error generating transaction template: %w", err)
+			return fmt.Errorf("error generating transaction template: %w", err)
 		}
 	default:
-		return nil, fmt.Errorf("invalid template type: %s", templateType)
+		return fmt.Errorf("invalid template type: %s", templateType)
 	}
 
 	directoryWithBasePath := filepath.Join(rootDir, basePath)
 	filenameWithBasePath := filepath.Join(rootDir, basePath, filename)
 
 	// Check file existence
-	if _, err := state.ReaderWriter().ReadFile(filenameWithBasePath); err == nil {
-		return nil, fmt.Errorf("file already exists: %s", filenameWithBasePath)
+	if _, err := g.state.ReaderWriter().ReadFile(filenameWithBasePath); err == nil {
+		return fmt.Errorf("file already exists: %s", filenameWithBasePath)
 	}
 
 	// Ensure the directory exists
-	if err := state.ReaderWriter().MkdirAll(directoryWithBasePath, 0755); err != nil {
-		return nil, fmt.Errorf("error creating directories: %w", err)
+	if err := g.state.ReaderWriter().MkdirAll(directoryWithBasePath, 0755); err != nil {
+		return fmt.Errorf("error creating directories: %w", err)
 	}
 
 	// Write files
-	err = state.ReaderWriter().WriteFile(filenameWithBasePath, []byte(fileToWrite), 0644)
+	err = g.state.ReaderWriter().WriteFile(filenameWithBasePath, []byte(fileToWrite), 0644)
 	if err != nil {
-		return nil, fmt.Errorf("error writing file: %w", err)
+		return fmt.Errorf("error writing file: %w", err)
 	}
 
-	logger.Info(fmt.Sprintf("Generated new %s: %s at %s", templateType, name, filenameWithBasePath))
+	if !g.disableLogs {
+		g.logger.Info(fmt.Sprintf("Generated new %s: %s at %s", templateType, name, filenameWithBasePath))
+	}
 
-	if generateFlags.SkipTests != true && templateType == "contract" {
+	if generateFlags.SkipTests != true && templateType == ContractType {
 		testDirectoryWithBasePath := filepath.Join(rootDir, testsBasePath)
-		testFilenameWithBasePath := filepath.Join(rootDir, testsBasePath, addCDCExtension(fmt.Sprintf("%s_test", name)))
+		testFilenameWithBasePath := filepath.Join(rootDir, testsBasePath, util.AddCDCExtension(fmt.Sprintf("%s_test", name)))
 
-		if _, err := state.ReaderWriter().ReadFile(testFilenameWithBasePath); err == nil {
-			return nil, fmt.Errorf("file already exists: %s", testFilenameWithBasePath)
+		if _, err := g.state.ReaderWriter().ReadFile(testFilenameWithBasePath); err == nil {
+			return fmt.Errorf("file already exists: %s", testFilenameWithBasePath)
 		}
 
-		if err := state.ReaderWriter().MkdirAll(testDirectoryWithBasePath, 0755); err != nil {
-			return nil, fmt.Errorf("error creating test directory: %w", err)
+		if err := g.state.ReaderWriter().MkdirAll(testDirectoryWithBasePath, 0755); err != nil {
+			return fmt.Errorf("error creating test directory: %w", err)
 		}
 
-		err = state.ReaderWriter().WriteFile(testFilenameWithBasePath, []byte(testFileToWrite), 0644)
+		err := g.state.ReaderWriter().WriteFile(testFilenameWithBasePath, []byte(testFileToWrite), 0644)
 		if err != nil {
-			return nil, fmt.Errorf("error writing test file: %w", err)
+			return fmt.Errorf("error writing test file: %w", err)
 		}
 
-		logger.Info(fmt.Sprintf("Generated new test file: %s at %s", name, testFilenameWithBasePath))
-	}
-
-	if templateType == "contract" {
-		var aliases config.Aliases
-
-		if generateFlags.SkipTests != true {
-			aliases = config.Aliases{{
-				Network: config.TestingNetwork.Name,
-				Address: flowsdk.HexToAddress("0x0000000000000007"),
-			}}
+		if !g.disableLogs {
+			g.logger.Info(fmt.Sprintf("Generated new test file: %s at %s", name, testFilenameWithBasePath))
 		}
+	}
 
-		contract := config.Contract{
-			Name:     name,
-			Location: filenameWithBasePath,
-			Aliases:  aliases,
-		}
-		state.Contracts().AddOrUpdate(contract)
-		err = state.SaveDefault()
+	if templateType == ContractType {
+		err := g.updateContractsState(name, filenameWithBasePath)
 		if err != nil {
-			return nil, fmt.Errorf("error saving to flow.json: %w", err)
+			return err
 		}
 	}
 
-	return nil, 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)
+	err := g.state.SaveDefault()
+	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/setup.go b/internal/super/setup.go
index 2c9f2cb1..685823ef 100644
--- a/internal/super/setup.go
+++ b/internal/super/setup.go
@@ -154,17 +154,14 @@ func startInteractiveSetup(
 
 	directoryPath := filepath.Join(tempDir, "cadence")
 
-	_, err = generateNew([]string{"DefaultContract"}, "contract", directoryPath, logger, state)
-	if err != nil {
-		return "", err
-	}
-
-	_, err = generateNew([]string{"DefaultScript"}, "script", directoryPath, logger, state)
-	if err != nil {
-		return "", err
+	templates := TemplateMap{
+		"contract":    []string{"DefaultContract"},
+		"script":      []string{"DefaultScripts"},
+		"transaction": []string{"DefaultTransaction"},
 	}
 
-	_, err = generateNew([]string{"DefaultTransaction"}, "transaction", directoryPath, logger, state)
+	generator := NewGenerator(directoryPath, state, logger, true)
+	err = generator.Create(templates)
 	if err != nil {
 		return "", err
 	}
diff --git a/internal/util/files.go b/internal/util/files.go
new file mode 100644
index 00000000..ce011bc1
--- /dev/null
+++ b/internal/util/files.go
@@ -0,0 +1,36 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019 Dapper Labs, Inc.
+ *
+ * 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 util
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+)
+
+func AddCDCExtension(name string) string {
+	if strings.HasSuffix(name, ".cdc") {
+		return name
+	}
+	return fmt.Sprintf("%s.cdc", name)
+}
+
+func StripCDCExtension(name string) string {
+	return strings.TrimSuffix(name, filepath.Ext(name))
+}


Test patch:
diff --git a/internal/super/generate_test.go b/internal/super/generate_test.go
index 951d6ed9..5a9400cc 100644
--- a/internal/super/generate_test.go
+++ b/internal/super/generate_test.go
@@ -34,8 +34,10 @@ func TestGenerateNewContract(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
+	generator := NewGenerator("cadence", state, logger, false)
+
 	// Test contract generation
-	_, err := generateNew([]string{"TestContract"}, "contract", "", logger, state)
+	err := generator.Create(TemplateMap{"contract": []string{"TestContract"}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	fileContent, err := state.ReaderWriter().ReadFile("cadence/contracts/TestContract.cdc")
@@ -70,7 +72,8 @@ access(all) fun testContract() {
 	assert.Equal(t, expectedTestContent, util.NormalizeLineEndings(string(testContent)))
 
 	// Test file already exists scenario
-	_, err = generateNew([]string{"TestContract"}, "contract", "", logger, state)
+	generatorTwo := NewGenerator("cadence", state, logger, false)
+	err = generatorTwo.Create(TemplateMap{"contract": []string{"TestContract"}})
 	assert.Error(t, err)
 	expectedError := fmt.Sprintf("file already exists: %s", filepath.FromSlash("cadence/contracts/TestContract.cdc"))
 	assert.Equal(t, expectedError, err.Error())
@@ -82,12 +85,13 @@ func TestGenerateNewContractSkipTests(t *testing.T) {
 
 	generateFlags.SkipTests = true
 
+	generator := NewGenerator("cadence", state, logger, false)
 	t.Cleanup(func() {
 		generateFlags.SkipTests = false
 	})
 
 	// Test contract generation
-	_, err := generateNew([]string{"TestContract"}, "contract", "", logger, state)
+	err := generator.Create(TemplateMap{"contract": []string{"TestContract"}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	fileContent, err := state.ReaderWriter().ReadFile("cadence/contracts/TestContract.cdc")
@@ -104,7 +108,8 @@ func TestGenerateNewContractWithCDCExtension(t *testing.T) {
 	_, state, _ := util.TestMocks(t)
 
 	// Test contract generation
-	_, err := generateNew([]string{"Tester.cdc"}, "contract", "", logger, state)
+	generator := NewGenerator("cadence", state, logger, false)
+	err := generator.Create(TemplateMap{"contract": []string{"Tester.cdc"}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	fileContent, err := state.ReaderWriter().ReadFile("cadence/contracts/Tester.cdc")
@@ -121,7 +126,8 @@ func TestGenerateNewContractFileAlreadyExists(t *testing.T) {
 	_, state, _ := util.TestMocks(t)
 
 	// Test contract generation
-	_, err := generateNew([]string{"TestContract"}, "contract", "", logger, state)
+	generator := NewGenerator("cadence", state, logger, false)
+	err := generator.Create(TemplateMap{"contract": []string{"TestContract"}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	//// Check if the file exists in the correct directory
@@ -130,7 +136,8 @@ func TestGenerateNewContractFileAlreadyExists(t *testing.T) {
 	assert.NotNil(t, content)
 
 	// Test file already exists scenario
-	_, err = generateNew([]string{"TestContract"}, "contract", "", logger, state)
+	generatorTwo := NewGenerator("cadence", state, logger, false)
+	err = generatorTwo.Create(TemplateMap{"contract": []string{"TestContract"}})
 	assert.Error(t, err)
 	expectedError := fmt.Sprintf("file already exists: %s", filepath.FromSlash("cadence/contracts/TestContract.cdc"))
 	assert.Equal(t, expectedError, err.Error())
@@ -140,7 +147,8 @@ func TestGenerateNewContractWithFileExtension(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
-	_, err := generateNew([]string{"TestContract.cdc"}, "contract", "", logger, state)
+	generator := NewGenerator("cadence", state, logger, false)
+	err := generator.Create(TemplateMap{"contract": []string{"TestContract.cdc"}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	// Check file exists
@@ -153,7 +161,8 @@ func TestGenerateNewScript(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
-	_, err := generateNew([]string{"TestScript"}, "script", "", logger, state)
+	generator := NewGenerator("cadence", state, logger, false)
+	err := generator.Create(TemplateMap{"script": []string{"TestScript"}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	content, err := state.ReaderWriter().ReadFile("cadence/scripts/TestScript.cdc")
@@ -171,7 +180,8 @@ func TestGenerateNewTransaction(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
-	_, err := generateNew([]string{"TestTransaction"}, "transaction", "", logger, state)
+	generator := NewGenerator("cadence", state, logger, false)
+	err := generator.Create(TemplateMap{"transaction": []string{"TestTransaction"}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	content, err := state.ReaderWriter().ReadFile("cadence/transactions/TestTransaction.cdc")
@@ -190,7 +200,8 @@ func TestGenerateNewWithDirFlag(t *testing.T) {
 	logger := output.NewStdoutLogger(output.NoneLog)
 	_, state, _ := util.TestMocks(t)
 
-	_, err := generateNew([]string{"TestContract"}, "contract", "customDir", logger, state)
+	generator := NewGenerator("customDir", state, logger, false)
+	err := generator.Create(TemplateMap{"contract": []string{"TestContract"}})
 	assert.NoError(t, err, "Failed to generate contract")
 
 	content, err := state.ReaderWriter().ReadFile("customDir/contracts/TestContract.cdc")