1package survey
2
3import (
4	"errors"
5
6	"gopkg.in/AlecAivazis/survey.v1/core"
7)
8
9// PageSize is the default maximum number of items to show in select/multiselect prompts
10var PageSize = 7
11
12// Validator is a function passed to a Question after a user has provided a response.
13// If the function returns an error, then the user will be prompted again for another
14// response.
15type Validator func(ans interface{}) error
16
17// Transformer is a function passed to a Question after a user has provided a response.
18// The function can be used to implement a custom logic that will result to return
19// a different representation of the given answer.
20//
21// Look `TransformString`, `ToLower` `Title` and `ComposeTransformers` for more.
22type Transformer func(ans interface{}) (newAns interface{})
23
24// Question is the core data structure for a survey questionnaire.
25type Question struct {
26	Name      string
27	Prompt    Prompt
28	Validate  Validator
29	Transform Transformer
30}
31
32// Prompt is the primary interface for the objects that can take user input
33// and return a response.
34type Prompt interface {
35	Prompt() (interface{}, error)
36	Cleanup(interface{}) error
37	Error(error) error
38}
39
40/*
41AskOne performs the prompt for a single prompt and asks for validation if required.
42Response types should be something that can be casted from the response type designated
43in the documentation. For example:
44
45	name := ""
46	prompt := &survey.Input{
47		Message: "name",
48	}
49
50	survey.AskOne(prompt, &name, nil)
51
52*/
53func AskOne(p Prompt, response interface{}, v Validator) error {
54	err := Ask([]*Question{{Prompt: p, Validate: v}}, response)
55	if err != nil {
56		return err
57	}
58
59	return nil
60}
61
62/*
63Ask performs the prompt loop, asking for validation when appropriate. The response
64type can be one of two options. If a struct is passed, the answer will be written to
65the field whose name matches the Name field on the corresponding question. Field types
66should be something that can be casted from the response type designated in the
67documentation. Note, a survey tag can also be used to identify a Otherwise, a
68map[string]interface{} can be passed, responses will be written to the key with the
69matching name. For example:
70
71	qs := []*survey.Question{
72		{
73			Name:     "name",
74			Prompt:   &survey.Input{Message: "What is your name?"},
75			Validate: survey.Required,
76			Transform: survey.Title,
77		},
78	}
79
80	answers := struct{ Name string }{}
81
82
83	err := survey.Ask(qs, &answers)
84*/
85func Ask(qs []*Question, response interface{}) error {
86
87	// if we weren't passed a place to record the answers
88	if response == nil {
89		// we can't go any further
90		return errors.New("cannot call Ask() with a nil reference to record the answers")
91	}
92
93	// go over every question
94	for _, q := range qs {
95		// grab the user input and save it
96		ans, err := q.Prompt.Prompt()
97		// if there was a problem
98		if err != nil {
99			return err
100		}
101
102		// if there is a validate handler for this question
103		if q.Validate != nil {
104			// wait for a valid response
105			for invalid := q.Validate(ans); invalid != nil; invalid = q.Validate(ans) {
106				err := q.Prompt.Error(invalid)
107				// if there was a problem
108				if err != nil {
109					return err
110				}
111
112				// ask for more input
113				ans, err = q.Prompt.Prompt()
114				// if there was a problem
115				if err != nil {
116					return err
117				}
118			}
119		}
120
121		if q.Transform != nil {
122			// check if we have a transformer available, if so
123			// then try to acquire the new representation of the
124			// answer, if the resulting answer is not nil.
125			if newAns := q.Transform(ans); newAns != nil {
126				ans = newAns
127			}
128		}
129
130		// tell the prompt to cleanup with the validated value
131		q.Prompt.Cleanup(ans)
132
133		// if something went wrong
134		if err != nil {
135			// stop listening
136			return err
137		}
138
139		// add it to the map
140		err = core.WriteAnswer(response, q.Name, ans)
141		// if something went wrong
142		if err != nil {
143			return err
144		}
145
146	}
147	// return the response
148	return nil
149}
150
151// paginate returns a single page of choices given the page size, the total list of
152// possible choices, and the current selected index in the total list.
153func paginate(page int, choices []string, sel int) ([]string, int) {
154	// the number of elements to show in a single page
155	var pageSize int
156	// if the select has a specific page size
157	if page != 0 {
158		// use the specified one
159		pageSize = page
160		// otherwise the select does not have a page size
161	} else {
162		// use the package default
163		pageSize = PageSize
164	}
165
166	var start, end, cursor int
167
168	if len(choices) < pageSize {
169		// if we dont have enough options to fill a page
170		start = 0
171		end = len(choices)
172		cursor = sel
173
174	} else if sel < pageSize/2 {
175		// if we are in the first half page
176		start = 0
177		end = pageSize
178		cursor = sel
179
180	} else if len(choices)-sel-1 < pageSize/2 {
181		// if we are in the last half page
182		start = len(choices) - pageSize
183		end = len(choices)
184		cursor = sel - start
185
186	} else {
187		// somewhere in the middle
188		above := pageSize / 2
189		below := pageSize - above
190
191		cursor = pageSize / 2
192		start = sel - above
193		end = sel + below
194	}
195
196	// return the subset we care about and the index
197	return choices[start:end], cursor
198}
199