Example #1
0
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
}
Example #2
0
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!")
	}
}
Example #3
0
// 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"))
	}
}
Example #4
0
// 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
}
Example #5
0
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
}