From dccbb30a85231f8b92636248a9ad3452e6d0cbbe Mon Sep 17 00:00:00 2001 From: Matthieu 'JP' DERASSE Date: Fri, 16 Sep 2022 20:22:19 +0000 Subject: [PATCH] feat(input): Full refacto of the user input system --- cmd/init.go | 15 +- cmd/upgrade.go | 13 +- .../api_types/go_swagger/get_user_input.go | 5 +- helpers/input.go | 158 ------------------ helpers/input/bool.go | 29 ++++ helpers/input/input.go | 40 +++++ helpers/input/other.go | 51 ++++++ helpers/input/string.go | 66 ++++++++ 8 files changed, 204 insertions(+), 173 deletions(-) delete mode 100644 helpers/input.go create mode 100644 helpers/input/bool.go create mode 100644 helpers/input/input.go create mode 100644 helpers/input/other.go create mode 100644 helpers/input/string.go diff --git a/cmd/init.go b/cmd/init.go index 90bd0ac..65a2019 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -15,6 +15,7 @@ import ( "git.home.m-and-m.ovh/mderasse/gouick/helpers" "git.home.m-and-m.ovh/mderasse/gouick/helpers/api_types" + "git.home.m-and-m.ovh/mderasse/gouick/helpers/input" "git.home.m-and-m.ovh/mderasse/gouick/models" log "github.com/sirupsen/logrus" @@ -99,7 +100,7 @@ func runInitAction(cmd *cobra.Command, args []string) { } log.Infof("Which kind of API do you want to init (possible values: %s)", strings.Join(possibleAPITypes, ", ")) - apiTypeName := helpers.APITypeNameInput() + apiTypeName := input.APITypeName(true) log.Debugf("Using api type : %s", string(apiTypeName)) @@ -120,27 +121,27 @@ func runInitAction(cmd *cobra.Command, args []string) { // ask project directory log.Info("Name of the project directory:") - projectDirectory := helpers.AlphanumericalInput() + projectDirectory := input.Alphanumerical(true) // ask project name log.Info("Name of the project:") - config.ProjectName = helpers.AlphanumericalAndSpaceInput() + config.ProjectName = input.AlphanumericalAndSpace(true) // ask project description log.Info("Description of the project:") - config.ProjectDescription = helpers.StringInput() + config.ProjectDescription = input.String(true) // ask project contact config.ProjectContact = models.ProjectContactStruct{} log.Info("Mail address of the developer team:") - config.ProjectContact.Email = helpers.StringInput() + config.ProjectContact.Email = input.Mail(true) log.Info("Name of the team:") - config.ProjectContact.Name = helpers.StringInput() + config.ProjectContact.Name = input.String(true) log.Info("URL of the contact informatino of the team:") - config.ProjectContact.URL = helpers.StringInput() + config.ProjectContact.URL = input.String(false) // launch GetInitializeUserInput from selected api type log.Debug("Get user input for the selected API type") diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 7a749f6..d033281 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "git.home.m-and-m.ovh/mderasse/gouick/helpers" + "git.home.m-and-m.ovh/mderasse/gouick/helpers/input" log "github.com/sirupsen/logrus" ) @@ -55,7 +56,7 @@ func runUpgradeAction(cmd *cobra.Command, args []string) { if !acceptAll { log.Infof("Do you want to install the following version: %s", dependency.GetMinimumVersion()) - answer := helpers.YesOrNoInput() + answer := input.YesOrNo() if !answer { log.Warnf("Skipping installation of %s. Some part of the application might not be able to work correctly!", dependency.GetName()) continue @@ -74,10 +75,10 @@ func runUpgradeAction(cmd *cobra.Command, args []string) { if !acceptAll { log.Infof("Do you want to install %s in the directory %s ?", dependency.GetName(), installDirectory) - answer := helpers.YesOrNoInput() + answer := input.YesOrNo() if !answer { log.Infof("Where do you want to install %s ?", dependency.GetName()) - installDirectory = helpers.PathInput() + installDirectory = input.Path(true) } } else { log.Infof("Installing %s in the following directory: %s", dependency.GetName(), installDirectory) @@ -93,12 +94,12 @@ func runUpgradeAction(cmd *cobra.Command, args []string) { return } log.Infof("Where do you want to install %s ?", dependency.GetName()) - installDirectory = helpers.PathInput() + installDirectory = input.Path(true) } log.Infof("%s", dependency.DescribeInstall(installDirectory)) log.Info("Do you want to continue ?") - answer := helpers.YesOrNoInput() + answer := input.YesOrNo() if !answer { log.Warnf("Skipping installation of %s. Some part of the application might not be able to work correctly!", dependency.GetName()) continue @@ -114,7 +115,7 @@ func runUpgradeAction(cmd *cobra.Command, args []string) { log.Infof("%s", dependency.DescribePostInstall(installDirectory)) log.Info("Do you want to continue ?") - answer = helpers.YesOrNoInput() + answer = input.YesOrNo() if !answer { log.Warnf("Skipping post installation of %s. Some part of the application might not be able to work correctly!", dependency.GetName()) continue diff --git a/helpers/api_types/go_swagger/get_user_input.go b/helpers/api_types/go_swagger/get_user_input.go index 586d13c..ba9671d 100644 --- a/helpers/api_types/go_swagger/get_user_input.go +++ b/helpers/api_types/go_swagger/get_user_input.go @@ -6,6 +6,7 @@ import ( "github.com/juju/errors" "git.home.m-and-m.ovh/mderasse/gouick/helpers" + "git.home.m-and-m.ovh/mderasse/gouick/helpers/input" "git.home.m-and-m.ovh/mderasse/gouick/models" log "github.com/sirupsen/logrus" @@ -31,13 +32,13 @@ func (a APIType) GetInitializeUserInput(params *models.Config) (*models.Config, if !isInGoPath { log.Debug("We are not in GoPath, ask extra info") - goModulePath := helpers.StringInput() + goModulePath := input.String(true) log.Info("Go Module path:") params.ModulePath = &goModulePath } log.Info("Do you want to enable database models auto generation ?") - params.Features.DatabaseModels.Enabled = helpers.YesOrNoInput() + params.Features.DatabaseModels.Enabled = input.YesOrNo() return params, nil } diff --git a/helpers/input.go b/helpers/input.go deleted file mode 100644 index a501907..0000000 --- a/helpers/input.go +++ /dev/null @@ -1,158 +0,0 @@ -package helpers - -import ( - "bufio" - "os" - "regexp" - "strings" - - "git.home.m-and-m.ovh/mderasse/gouick/models" - - log "github.com/sirupsen/logrus" -) - -// YesOrNoInput ask user for a yes or no reply and try until we get a possible answer. -func YesOrNoInput() bool { - - scanner := bufio.NewScanner(os.Stdin) - for { - scanner.Scan() - userInput := scanner.Text() - - lUserInput := strings.ToLower(userInput) - - for _, positiveAnswer := range []string{"yes", "y", "1", "true"} { - if lUserInput == positiveAnswer { - return true - } - } - for _, negativeAnswer := range []string{"no", "n", "0", "false"} { - if lUserInput == negativeAnswer { - return false - } - } - - log.Info("Expecting a yes or no answer, Try again") - } -} - -// AlphanumericalInput ask user for a alphanumerical string and try until we get a possible answer. -func AlphanumericalInput() string { - - scanner := bufio.NewScanner(os.Stdin) - for { - - scanner.Scan() - userInput := scanner.Text() - - if userInput == "" { - log.Warn("Empty input, try again") - continue - } - - is_alphanumeric := regexp.MustCompile(`^[a-zA-Z0-9\-\_]*$`).MatchString(userInput) - - if !is_alphanumeric { - log.Warn("Please use a-z, A-Z, 0-9 and - or _ characters only.") - continue - } - - return userInput - } -} - -// AlphanumericalAndSpaceInput ask user for a alphanumerical+space string and try until we get a possible answer. -func AlphanumericalAndSpaceInput() string { - - scanner := bufio.NewScanner(os.Stdin) - for { - - scanner.Scan() - userInput := scanner.Text() - - if userInput == "" { - log.Warn("Empty input, try again") - continue - } - - is_alphanumeric := regexp.MustCompile(`^[a-zA-Z0-9\-\_\s]*$`).MatchString(userInput) - - if !is_alphanumeric { - log.Warn("Please use a-z, A-Z, 0-9, - , space or _ characters only.") - continue - } - - return userInput - } -} - -// StringInput ask user for a string and try until we get a possible answer. -func StringInput() string { - - scanner := bufio.NewScanner(os.Stdin) - for { - - scanner.Scan() - userInput := scanner.Text() - - if userInput == "" { - log.Warn("Empty input, try again") - continue - } - - return userInput - } -} - -// PathInput ask user for a path string and try until we get a possible answer. -func PathInput() string { - - scanner := bufio.NewScanner(os.Stdin) - for { - - scanner.Scan() - userInput := scanner.Text() - - if userInput == "" { - log.Warn("Empty input, try again") - continue - } - - _, err := CheckAndCreateDir(userInput) - if err != nil { - log.Warnf("please, try again") - continue - } - - return userInput - } -} - -// APITypeNameInput ask user for a string that is a valid API Type name and try until we get a possible answer. -func APITypeNameInput() models.APITypeName { - - possibleAPITypes := make([]string, 0) - for _, apiType := range models.GetListOfAPITypeName() { - possibleAPITypes = append(possibleAPITypes, string(apiType)) - } - - scanner := bufio.NewScanner(os.Stdin) - for { - - scanner.Scan() - userInput := scanner.Text() - - if userInput == "" { - log.Warn("Empty input, try again") - continue - } - - apiTypeName, err := models.NewAPITypeNameFromInput(userInput) - if err != nil { - log.Warnf("invalid API type (possible values: %s)", strings.Join(possibleAPITypes, ", ")) - continue - } - - return apiTypeName - } -} diff --git a/helpers/input/bool.go b/helpers/input/bool.go new file mode 100644 index 0000000..788d8bc --- /dev/null +++ b/helpers/input/bool.go @@ -0,0 +1,29 @@ +package input + +import ( + "fmt" + "strings" +) + +// YesOrNo ask user for a yes or no reply and try until we get a possible answer. +func YesOrNo() bool { + + value := Ask(true, func(in string) (string, error) { + lUserInput := strings.ToLower(in) + + for _, positiveAnswer := range []string{"yes", "y", "1", "true"} { + if lUserInput == positiveAnswer { + return "y", nil + } + } + for _, negativeAnswer := range []string{"no", "n", "0", "false"} { + if lUserInput == negativeAnswer { + return "n", nil + } + } + + return "", fmt.Errorf("expecting a yes or no answer, try again") + }) + + return value == "y" +} diff --git a/helpers/input/input.go b/helpers/input/input.go new file mode 100644 index 0000000..4252a99 --- /dev/null +++ b/helpers/input/input.go @@ -0,0 +1,40 @@ +package input + +import ( + "bufio" + "os" + + log "github.com/sirupsen/logrus" +) + +// Validator represent signature of the input validation function. +type Validator func(in string) (string, error) + +// Ask will ask user for an input and check validity with provided fn function. +func Ask(mandatory bool, fn Validator) string { + scanner := bufio.NewScanner(os.Stdin) + for { + + scanner.Scan() + userInput := scanner.Text() + + if userInput == "" { + + if mandatory { + log.Warn("Empty input, try again") + continue + } + + return userInput + } + + value, err := fn(userInput) + + if err != nil { + log.Warnf("%s", err.Error()) + continue + } + + return value + } +} diff --git a/helpers/input/other.go b/helpers/input/other.go new file mode 100644 index 0000000..0a45b1d --- /dev/null +++ b/helpers/input/other.go @@ -0,0 +1,51 @@ +package input + +import ( + "fmt" + "path/filepath" + "strings" + + "git.home.m-and-m.ovh/mderasse/gouick/helpers" + "git.home.m-and-m.ovh/mderasse/gouick/models" +) + +// Path ask user for a path string and try until we get a possible answer. +func Path(mandatory bool) string { + + value := Ask(mandatory, func(in string) (string, error) { + + path := filepath.Clean(in) + + _, err := helpers.CheckAndCreateDir(path) + if err != nil { + return "", fmt.Errorf("please try again") + } + + return path, nil + }) + + return value +} + +// APITypeName ask user for a string that is a valid API Type name and try until we get a possible answer. +func APITypeName(mandatory bool) models.APITypeName { + + possibleAPITypes := make([]string, 0) + for _, apiType := range models.GetListOfAPITypeName() { + possibleAPITypes = append(possibleAPITypes, string(apiType)) + } + + value := Ask(mandatory, func(in string) (string, error) { + + apiTypeName, err := models.NewAPITypeNameFromInput(in) + if err != nil { + return "", fmt.Errorf("invalid API type (possible values: %s)", strings.Join(possibleAPITypes, ", ")) + } + + return string(apiTypeName), nil + }) + + apiTypeName, _ := models.NewAPITypeName(value) + + return apiTypeName +} diff --git a/helpers/input/string.go b/helpers/input/string.go new file mode 100644 index 0000000..e5150ac --- /dev/null +++ b/helpers/input/string.go @@ -0,0 +1,66 @@ +package input + +import ( + "fmt" + "net/mail" + "regexp" +) + +var ( + // RegexAlphanumerical is a basic alphanumerical regex to be use in RegexInput function. + RegexAlphanumerical = regexp.MustCompile(`^[a-zA-Z0-9\-\_]*$`) + // RegexAlphanumericalAndSpace is a basic alphanumerical regex with space as an additionnal valid character to be use in RegexInput function. + RegexAlphanumericalAndSpace = regexp.MustCompile(`^[a-zA-Z0-9\-\_\s]*$`) +) + +// Regex permit to ask user for an input and check that it match the given regex rule. +// if not user will see the errMsg and be asked for another input. +func Regex(mandatory bool, regex *regexp.Regexp, errMsg string) string { + + value := Ask(mandatory, func(in string) (string, error) { + + if !regex.MatchString(in) { + return "", fmt.Errorf("%s", errMsg) + } + + return in, nil + }) + + return value +} + +// Alphanumerical will ask user for an input respecting the RegexAlphanumerical. +func Alphanumerical(mandatory bool) string { + return Regex(mandatory, RegexAlphanumerical, "Please use a-z, A-Z, 0-9, - and _ characters only.") +} + +// AlphanumericalAndSpace will ask user for an input respecting the RegexAlphanumericalAndSpace. +func AlphanumericalAndSpace(mandatory bool) string { + return Regex(mandatory, RegexAlphanumericalAndSpace, "Please use a-z, A-Z, 0-9, -, _ and space characters only.") +} + +// String permit to ask user for any kind of string. +func String(mandatory bool) string { + + value := Ask(mandatory, func(in string) (string, error) { + return in, nil + }) + + return value +} + +// Mail permit to ask user for an email address. +func Mail(mandatory bool) string { + + value := Ask(mandatory, func(in string) (string, error) { + + _, err := mail.ParseAddress(in) + if err != nil { + return "", fmt.Errorf("expecting a valid mail address, please try again") + } + + return in, nil + }) + + return value +}