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

Adding test and some refactor #77

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 48 additions & 68 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,20 @@ const logo = `

`

var AllowedProjectTypes = []string{"chi", "gin", "fiber", "gorilla/mux", "httprouter", "standard-library", "echo"}

var (
logoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#01FAC6")).Bold(true)
tipMsgStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("190")).Italic(true)
endingMsgStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("170")).Bold(true)
allowedProjectTypes = []string{"chi", "gin", "fiber", "gorilla/mux", "httprouter", "standard-library", "echo"}
logoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#01FAC6")).Bold(true)
tipMsgStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("190")).Italic(true)
separatorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
endingMsgStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("170")).Bold(true)
)

func init() {
rootCmd.AddCommand(createCmd)

createCmd.Flags().StringP("name", "n", "", "Name of project to create")
createCmd.Flags().StringP("framework", "f", "", fmt.Sprintf("Framework to use. Allowed values: %s", strings.Join(allowedProjectTypes, ", ")))
createCmd.Flags().StringP("framework", "f", "", fmt.Sprintf("Framework to use. Allowed values: %s", strings.Join(AllowedProjectTypes, ", ")))
}

// createCmd defines the "create" command for the CLI
Expand All @@ -50,65 +52,14 @@ var createCmd = &cobra.Command{
Long: "Go Blueprint is a CLI tool that allows you to focus on the actual Go code, and not the project structure. Perfect for someone new to the Go language",

Run: func(cmd *cobra.Command, args []string) {
var tprogram *tea.Program

options := steps.Options{
ProjectName: &textinput.Output{},
}

isInteractive := !utils.HasChangedFlag(cmd.Flags())

flagName := cmd.Flag("name").Value.String()
flagFramework := cmd.Flag("framework").Value.String()

if flagFramework != "" {
isValid := isValidProjectType(flagFramework, allowedProjectTypes)
if !isValid {
cobra.CheckErr(fmt.Errorf("Project type '%s' is not valid. Valid types are: %s", flagFramework, strings.Join(allowedProjectTypes, ", ")))
}
}

project := &program.Project{
FrameworkMap: make(map[string]program.Framework),
ProjectName: flagName,
ProjectType: strings.ReplaceAll(flagFramework, "-", " "),
}

steps := steps.InitSteps(&options)
fmt.Printf("%s\n", logoStyle.Render(logo))
project, options, isInteractive := NewProject(cmd)

if project.ProjectName == "" {
tprogram := tea.NewProgram(textinput.InitialTextInputModel(options.ProjectName, "What is the name of your project?", project))
if _, err := tprogram.Run(); err != nil {
log.Printf("Name of project contains an error: %v", err)
cobra.CheckErr(err)
}
project.ExitCLI(tprogram)

project.ProjectName = options.ProjectName.Output
err := cmd.Flag("name").Value.Set(project.ProjectName)
if err != nil {
log.Fatal("failed to set the name flag value", err)
}
createNamePrompt(cmd, project, &options)
}

if project.ProjectType == "" {
for _, step := range steps.Steps {
s := &multiInput.Selection{}
tprogram = tea.NewProgram(multiInput.InitialModelMulti(step.Options, s, step.Headers, project))
if _, err := tprogram.Run(); err != nil {
cobra.CheckErr(err)
}
project.ExitCLI(tprogram)

*step.Field = s.Choice
}

project.ProjectType = strings.ToLower(options.ProjectType)
err := cmd.Flag("framework").Value.Set(project.ProjectType)
if err != nil {
log.Fatal("failed to set the framework flag value", err)
}
createFrameworkPrompt(cmd, project, &options)
}

currentWorkingDir, err := os.Getwd()
Expand All @@ -126,24 +77,53 @@ var createCmd = &cobra.Command{
cobra.CheckErr(err)
}

fmt.Println(endingMsgStyle.Render("\nNext steps cd into the newly created project with:"))
fmt.Println(separatorStyle.Render(strings.Repeat("-", 80)))
fmt.Println(endingMsgStyle.Render("\nNext step: navigate into the newly created project with:"))
fmt.Println(endingMsgStyle.Render(fmt.Sprintf("• cd %s\n", project.ProjectName)))

if isInteractive {
nonInteractiveCommand := utils.NonInteractiveCommand(cmd.Flags())
fmt.Println(tipMsgStyle.Render("Tip: Repeat the equivalent Blueprint with the following non-interactive command:"))
fmt.Println(tipMsgStyle.Render("Tip: go-blueprint supports non-interactive commands:"))
fmt.Println(tipMsgStyle.Italic(false).Render(fmt.Sprintf("• %s\n", nonInteractiveCommand)))
}
},
}

// isValidProjectType checks if the inputted project type matches
// the currently supported list of project types
func isValidProjectType(input string, allowedTypes []string) bool {
for _, t := range allowedTypes {
if input == t {
return true
func createNamePrompt(cmd *cobra.Command, project *program.Project, options *steps.Options) {
tprogram := tea.NewProgram(textinput.InitialTextInputModel(options.ProjectName, "What is the name of your project?", project))
if _, err := tprogram.Run(); err != nil {
log.Printf("Name of project contains an error: %v", err)
cobra.CheckErr(err)
}
project.ExitCLI(tprogram)

project.ProjectName = options.ProjectName.Output
err := cmd.Flag("name").Value.Set(project.ProjectName)
if err != nil {
log.Printf("Problem setting project name: %v", err)
cobra.CheckErr(err)
}
}

func createFrameworkPrompt(cmd *cobra.Command, project *program.Project, options *steps.Options) {
steps := steps.InitSteps(options)
fmt.Printf("%s\n", logoStyle.Render(logo))

for _, step := range steps.Steps {
s := &multiInput.Selection{}
tprogram := tea.NewProgram(multiInput.InitialModelMulti(step.Options, s, step.Headers, project))
if _, err := tprogram.Run(); err != nil {
cobra.CheckErr(err)
}
project.ExitCLI(tprogram)

*step.Field = s.Choice
}

project.ProjectType = strings.ToLower(options.ProjectType)
err := cmd.Flag("framework").Value.Set(project.ProjectType)
if err != nil {
log.Printf("Problem setting project framework: %v", err)
cobra.CheckErr(err)
}
return false
}
42 changes: 42 additions & 0 deletions cmd/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cmd_test

import (
"strings"
"testing"

"github.com/melkeydev/go-blueprint/cmd"
"github.com/melkeydev/go-blueprint/cmd/program"
)

func TestCheckMissingProjectTypes(t *testing.T) {
project := program.Project{
FrameworkMap: make(map[string]program.Framework),
}

project.CreateFrameworkMap()

missingTypes := checkMissingProjectTypes(project.FrameworkMap, cmd.AllowedProjectTypes)

if len(missingTypes) != 0 {
t.Errorf("Expected missingTypes to be empty, got %v", missingTypes)
}
}

func checkMissingProjectTypes(frameworkMap map[string]program.Framework, types []string) []string {
var missingTypes []string

for _, t := range types {
t := strings.ReplaceAll(t, "-", " ")

if _, ok := frameworkMap[t]; !ok {
missingTypes = append(missingTypes, t)
}

if _, ok := frameworkMap[t]; !ok {
missingTypes = append(missingTypes, t)
}

}

return missingTypes
}
5 changes: 3 additions & 2 deletions cmd/program/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log"
"os"
"strings"

tea "github.com/charmbracelet/bubbletea"
tpl "github.com/melkeydev/go-blueprint/cmd/template"
"github.com/melkeydev/go-blueprint/cmd/utils"
Expand Down Expand Up @@ -65,7 +66,7 @@ func (p *Project) ExitCLI(tprogram *tea.Program) {

// createFrameWorkMap adds the current supported
// Frameworks into a Project's FrameworkMap
func (p *Project) createFrameworkMap() {
func (p *Project) CreateFrameworkMap() {
p.FrameworkMap["chi"] = Framework{
packageName: chiPackage,
templater: tpl.ChiTemplates{},
Expand Down Expand Up @@ -128,7 +129,7 @@ func (p *Project) CreateMainFile() error {
projectPath := fmt.Sprintf("%s/%s", p.AbsolutePath, p.ProjectName)

// Create the map for our program
p.createFrameworkMap()
p.CreateFrameworkMap()

// Create go.mod
err := utils.InitGoMod(p.ProjectName, projectPath)
Expand Down
38 changes: 38 additions & 0 deletions cmd/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cmd

import (
"fmt"
"strings"

"github.com/melkeydev/go-blueprint/cmd/program"
"github.com/melkeydev/go-blueprint/cmd/steps"
"github.com/melkeydev/go-blueprint/cmd/ui/textinput"
"github.com/melkeydev/go-blueprint/cmd/utils"
"github.com/spf13/cobra"
)

func NewProject(cmd *cobra.Command) (*program.Project, steps.Options, bool) {
options := steps.Options{
ProjectName: &textinput.Output{},
}

flagName := utils.FlagValue(cmd, "name")
flagFramework := utils.FlagValue(cmd, "framework")

if flagFramework != "" {
isValid := utils.IsValidProjectType(flagFramework, AllowedProjectTypes)
if !isValid {
cobra.CheckErr(fmt.Errorf("Project type '%s' is not valid. Valid types are: %s", flagFramework, strings.Join(AllowedProjectTypes, ", ")))
}
}

isInteractive := !utils.HasChangedFlag(cmd.Flags())

project := &program.Project{
FrameworkMap: make(map[string]program.Framework),
ProjectName: flagName,
ProjectType: strings.ReplaceAll(flagFramework, "-", " "),
}

return project, options, isInteractive
}
4 changes: 0 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"github.com/spf13/cobra"
)



// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "go-blueprint",
Expand Down Expand Up @@ -39,5 +37,3 @@ func init() {
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}


7 changes: 4 additions & 3 deletions cmd/steps/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// each step of the CLI
package steps

import textinput "github.com/melkeydev/go-blueprint/cmd/ui/textinput"
import "github.com/melkeydev/go-blueprint/cmd/ui/textinput"

// A StepSchema contains the data that is used
// for an individual step of the CLI
Expand Down Expand Up @@ -61,8 +61,9 @@ func InitSteps(options *Options) *Steps {
Title: "HttpRouter",
Desc: "HttpRouter is a lightweight high performance HTTP request router for Go",
},
{Title: "Echo",
Desc: "High performance, extensible, minimalist Go web framework",
{
Title: "Echo",
Desc: "High performance, extensible, minimalist Go web framework",
},
},
Headers: "What framework do you want to use in your Go project?",
Expand Down
16 changes: 16 additions & 0 deletions cmd/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"os/exec"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

Expand Down Expand Up @@ -37,6 +38,21 @@ func HasChangedFlag(flagSet *pflag.FlagSet) bool {
return hasChangedFlag
}

func FlagValue(cmd *cobra.Command, flagName string) string {
return cmd.Flag(flagName).Value.String()
}

// isValidProjectType checks if the inputted project type matches
// the currently supported list of project types
func IsValidProjectType(input string, allowedTypes []string) bool {
for _, t := range allowedTypes {
if input == t {
return true
}
}
return false
}

// ExecuteCmd provides a shorthand way to run a shell command
func ExecuteCmd(name string, args []string, dir string) error {
command := exec.Command(name, args...)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.9.0
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
)

require (
Expand All @@ -23,7 +24,6 @@ require (
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/term v0.6.0 // indirect
Expand Down
Loading