survey.go 6.1 KB

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