func promptForLabelList(msg string, defaultItems, implicitItems []string) ([]string, error) { var ( lenDefault = len(defaultItems) lenImplicit = len(implicitItems) ) // Prompt for the value. fmt.Printf("%v, comma-separated.\n", msg) if lenDefault != 0 { fmt.Printf(" (default values: %v)\n", strings.Join(defaultItems, ", ")) } if lenImplicit != 0 { fmt.Printf(" (always included: %v)\n", strings.Join(implicitItems, ", ")) } fmt.Println() input, err := prompt.Prompt("Your choice: ") if err != nil { if err == prompt.ErrCanceled { return append(defaultItems, implicitItems...), nil } return nil, err } // Append the new labels to the default ones. // Make sure there are no duplicates and empty strings. var ( insertedLabels = strings.Split(input, ",") lenInserted = len(insertedLabels) ) // Save a few allocations. labels := make([]string, lenImplicit, lenImplicit+lenInserted) copy(labels, implicitItems) LabelLoop: for _, insertedLabel := range insertedLabels { // Trim spaces. insertedLabel = strings.TrimSpace(insertedLabel) // Skip empty strings. if insertedLabel == "" { continue } // Make sure there are no duplicates. for _, existingLabel := range labels { if insertedLabel == existingLabel { continue LabelLoop } } // Append the label. labels = append(labels, insertedLabel) } return labels, nil }
func promptUserToSelectModule(modules []Module, optional bool) (Module, error) { // Prompt the user to select a module. kind := modules[0].Kind() for { // Write the dialog into the console. ctx := &dialogTemplateContext{ Kind: string(kind), Modules: modules, Optional: optional, } if err := dialogTemplate.Execute(os.Stdout, ctx); err != nil { return nil, err } // Prompt the user for the answer. // An empty answer is aborting the dialog. answer, err := prompt.Prompt("You choice: ") if err != nil { if err == prompt.ErrCanceled { prompt.PanicCancel() } return nil, err } if optional && answer == "s" { fmt.Println() color.Cyan("Skipping module kind '%v'", kind) return nil, nil } // Parse the index and return the associated module. i, err := strconv.Atoi(answer) if err == nil { if 0 < i && i <= len(modules) { return modules[i-1], nil } } // In case we failed to parse the index or something, run the dialog again. color.Yellow("Not a valid choice, please try again!") } }
// Run starts the dialog after the options are set. It uses the given story list // to prompt the user for a story using the given options. func (dialog *Dialog) Run(stories []common.Story) (common.Story, error) { // Return an error when no options are set. if len(dialog.opts) == 0 { return nil, errors.New("storyprompt.Dialog.Run(): no options were specified") } // Increment the dialog depth on enter. dialog.depth++ // Decrement the dialog depth on return. defer func() { dialog.depth-- }() // Enter the dialog loop. DialogLoop: for { var ( opts = dialog.opts depth = dialog.depth ) // Present the stories to the user. if err := ListStories(stories, os.Stdout); err != nil { return nil, err } // Print the options based on the dialog depth. fmt.Println() fmt.Println("Now you can do one of the following:") fmt.Println() // Collect the list of active options. activeOpts := make([]*DialogOption, 0, len(opts)) for _, opt := range opts { if isActive := opt.IsActive; isActive != nil && isActive(stories, depth) { activeOpts = append(activeOpts, opt) } } // Print the description for the active options. for _, opt := range activeOpts { if desc := opt.Description; len(desc) != 0 { fmt.Println(" -", strings.Join(desc, "\n ")) } } fmt.Println() // Prompt the user for their choice. fmt.Println("Current dialog depth:", depth) input, err := prompt.Prompt("Choose what to do next: ") // We ignore prompt.ErrCanceled here and simply continue. // That is because an empty input is a valid input here as well. if err != nil && err != prompt.ErrCanceled { return nil, err } input = strings.TrimSpace(input) // Find the first matching option. var matchingOpt *DialogOption for _, opt := range activeOpts { if matchFunc := opt.MatchesInput; matchFunc != nil && matchFunc(input, stories) { matchingOpt = opt break } } // Loop again in case no match is found. if matchingOpt == nil { fmt.Println() fmt.Println("Error: no matching option found") fmt.Println() continue DialogLoop } // Run the selected select function. if selectFunc := matchingOpt.SelectStory; selectFunc != nil { story, err := selectFunc(input, stories, dialog) if err != nil { switch err { case ErrContinue: // Continue looping on ErrContinue. fmt.Println() continue DialogLoop case ErrReturn: // Go one dialog up by returning ErrContinue. // This makes the dialog loop of the parent dialog continue, // effectively re-printing and re-running that dialog. if dialog.isSub { return nil, ErrContinue } // In case this is a top-level dialog, abort. fallthrough case ErrAbort: // Panic prompt.ErrCanceled on ErrAbort, returning immediately // from any dialog depth. prompt.PanicCancel() } // In case the error is not any of the recognized control errors, // print the error and loop again, making the user choose again. fmt.Println() fmt.Println("Error:", err) fmt.Println() continue DialogLoop } return story, nil } // No SelectStory function specified for the matching option, // that is a programming error, let's just panic. panic(errors.New("SelectStory function not specified")) } }
// PromptUserForConfig is a part of loader.ConfigContainer interface. func (local *LocalConfig) PromptUserForConfig() error { c := LocalConfig{spec: local.spec} // Prompt for the project ID. task := "Fetch available Pivotal Tracker projects" log.Run(task) client := pivotal.NewClient(local.spec.global.UserToken) projects, _, err := client.Projects.List() if err != nil { return errs.NewError(task, err) } sort.Sort(ptProjects(projects)) fmt.Println() fmt.Println("Available Pivotal Tracker projects:") fmt.Println() for i, project := range projects { fmt.Printf(" [%v] %v\n", i+1, project.Name) } fmt.Println() fmt.Println("Choose the project to associate this repository with.") index, err := prompt.PromptIndex("Project number: ", 1, len(projects)) if err != nil { if err == prompt.ErrCanceled { prompt.PanicCancel() } return err } fmt.Println() c.ProjectId = projects[index-1].Id // Prompt for the labels. promptForLabel := func(dst *string, labelName, defaultValue string) { if err != nil { return } question := fmt.Sprintf("%v label", labelName) var label string label, err = prompt.PromptDefault(question, defaultValue) if err == nil { *dst = label } } var componentLabel string promptForLabel(&componentLabel, "Component", "") c.ComponentLabel = &componentLabel promptForLabel(&c.Labels.PointMeLabel, "Point me", DefaultPointMeLabel) promptForLabel(&c.Labels.ReviewedLabel, "Reviewed", DefaultReviewedLabel) promptForLabel(&c.Labels.SkipReviewLabel, "Skip review", DefaultSkipReviewLabel) promptForLabel(&c.Labels.TestedLabel, "Testing passed", DefaultTestedLabel) promptForLabel(&c.Labels.SkipTestingLabel, "Skip testing", DefaultSkipTestingLabel) if err != nil { return err } // Prompt for the release skip check labels. skipCheckLabelsString, err := prompt.Prompt(fmt.Sprintf( "Skip check labels, comma-separated (%v always included): ", strings.Join(DefaultSkipCheckLabels, ", "))) if err != nil { if err != prompt.ErrCanceled { return err } } // Append the new labels to the default ones. // Make sure there are no duplicates and empty strings. var ( insertedLabels = strings.Split(skipCheckLabelsString, ",") lenDefault = len(DefaultSkipCheckLabels) lenInserted = len(insertedLabels) ) // Save a few allocations. skipCheckLabels := make([]string, lenDefault, lenDefault+lenInserted) copy(skipCheckLabels, DefaultSkipCheckLabels) LabelLoop: for _, insertedLabel := range insertedLabels { // Trim spaces. insertedLabel = strings.TrimSpace(insertedLabel) // Skip empty strings. if insertedLabel == "" { continue } // Make sure there are no duplicates. for _, existingLabel := range skipCheckLabels { if insertedLabel == existingLabel { continue LabelLoop } } // Append the label. skipCheckLabels = append(skipCheckLabels, insertedLabel) } c.Labels.SkipCheckLabels = skipCheckLabels // Success! *local = c return nil }
func createBranch() (action.Action, error) { // Get the current branch name. originalBranch, err := gitutil.CurrentBranch() if err != nil { return nil, err } // Fetch the remote repository. task := "Fetch the remote repository" log.Run(task) gitConfig, err := git.LoadConfig() if err != nil { return nil, errs.NewError(task, err) } var ( remoteName = gitConfig.RemoteName baseBranch = gitConfig.TrunkBranchName ) if flagBase != "" { baseBranch = flagBase } // Fetch the remote repository. if err := git.UpdateRemotes(remoteName); err != nil { return nil, errs.NewError(task, err) } // Make sure the trunk branch is up to date. task = fmt.Sprintf("Make sure branch '%v' is up to date", baseBranch) log.Run(task) if err := git.CheckOrCreateTrackingBranch(baseBranch, remoteName); err != nil { return nil, errs.NewError(task, err) } // Prompt the user for the branch name. task = "Prompt the user for the branch name" line, err := prompt.Prompt(` Please insert the branch slug now. Insert an empty string to skip the branch creation step: `) if err != nil && err != prompt.ErrCanceled { return nil, errs.NewError(task, err) } sluggedLine := slug.Slug(line) if sluggedLine == "" { fmt.Println() log.Log("Not creating any feature branch") return nil, nil } branchName := "story/" + sluggedLine ok, err := prompt.Confirm( fmt.Sprintf( "\nThe branch that is going to be created will be called '%s'.\nIs that alright?", branchName), true) if err != nil { return nil, errs.NewError(task, err) } if !ok { panic(prompt.ErrCanceled) } fmt.Println() createTask := fmt.Sprintf( "Create branch '%v' on top of branch '%v'", branchName, baseBranch) log.Run(createTask) if err := git.Branch(branchName, baseBranch); err != nil { return nil, errs.NewError(createTask, err) } deleteTask := fmt.Sprintf("Delete branch '%v'", branchName) deleteBranch := func() error { // Roll back and delete the newly created branch. log.Rollback(createTask) if err := git.Branch("-D", branchName); err != nil { return errs.NewError(deleteTask, err) } return nil } // Checkout the newly created branch. checkoutTask := fmt.Sprintf("Checkout branch '%v'", branchName) log.Run(checkoutTask) if err := git.Checkout(branchName); err != nil { if err := deleteBranch(); err != nil { errs.Log(err) } return nil, errs.NewError(checkoutTask, err) } // Push the newly created branch unless -no_push. pushTask := fmt.Sprintf("Push branch '%v' to remote '%v'", branchName, remoteName) if flagPush { log.Run(pushTask) if err := git.Push(remoteName, branchName); err != nil { if err := deleteBranch(); err != nil { errs.Log(err) } return nil, errs.NewError(pushTask, err) } } return action.ActionFunc(func() error { // Checkout the original branch. log.Rollback(checkoutTask) if err := git.Checkout(originalBranch); err != nil { return errs.NewError( fmt.Sprintf("Checkout the original branch '%v'", originalBranch), err) } // Delete the newly created branch. deleteErr := deleteBranch() // In case we haven't pushed anything, we are done. if !flagPush { return deleteErr } // Delete the branch from the remote repository. log.Rollback(pushTask) if _, err := git.Run("push", "--delete", remoteName, branchName); err != nil { // In case deleteBranch failed, tell the user now // since we are not going to return that error. if deleteErr != nil { errs.Log(deleteErr) } return errs.NewError( fmt.Sprintf("Delete branch '%v' from remote '%v'", branchName, remoteName), err) } // Return deleteErr to make sure it propagates up. return deleteErr }), nil }