user-input-output.scm 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. (library (user-input-output)
  2. (export read-line
  3. write-string
  4. remove-whitespace-chars
  5. ask-user
  6. ask-user-for-text
  7. ask-user-for-character
  8. ask-user-for-number
  9. ask-user-for-integer-number
  10. ask-user-for-yes-no-decision
  11. ask-user-for-decision
  12. ask-user-for-decision-with-continuations
  13. confirm-info-message
  14. string-format)
  15. (import
  16. (except (rnrs base) let-values map error)
  17. (only (guile)
  18. ;; lambda forms
  19. lambda* λ
  20. ;; conditionals
  21. when
  22. ;; input output
  23. simple-format
  24. current-output-port
  25. call-with-output-string
  26. current-input-port
  27. ;; strings
  28. string-trim
  29. string-join
  30. string-append
  31. string-delete
  32. ;; other
  33. error)
  34. (ice-9 textual-ports)
  35. (ice-9 optargs)
  36. ;; srfi-1 for list procedures
  37. (srfi srfi-1)))
  38. (define read-line
  39. (lambda* (#:optional (input-port (current-input-port)))
  40. (get-line input-port)))
  41. (define write-string
  42. (lambda* (string #:optional (output-port (current-output-port)))
  43. (put-string output-port string)))
  44. (define (remove-whitespace-chars string)
  45. (string-delete (lambda (char)
  46. (memq char '(#\newline #\tab #\return #\space)))
  47. string))
  48. (define trim-whitespace-chars
  49. (λ (string)
  50. "Trim whitespace characters from the left and right end
  51. of the given string."
  52. (string-trim string
  53. (lambda (char)
  54. (memq char '(#\newline #\tab #\return #\space))))))
  55. (define* (ask-user question pred
  56. #:key
  57. (input-cleanup-proc trim-whitespace-chars)
  58. (possible-answers #f)
  59. (q-a-separator ": ")
  60. (choices-opener "(")
  61. (choices-separator "/")
  62. (choices-closer ")")
  63. (question-to-choices-separator " ")
  64. (invalid-input-message "Invalid input.\n"))
  65. "Ask a question clean the input of its answer using the
  66. given INPUT-CLEANUP-PROC and check the cleaned answer using
  67. the given predicate PRED. Either provide POSSIBLE-ANSWERS or
  68. leave it at its default #f."
  69. (define ask-question
  70. (λ ()
  71. (write-string question)
  72. (when possible-answers
  73. (write-string (string-append question-to-choices-separator
  74. choices-opener
  75. (string-join possible-answers choices-separator)
  76. choices-closer))
  77. (write-string ""))
  78. (write-string q-a-separator)
  79. (read-line)))
  80. (let try-again ([input (ask-question)])
  81. (let ([cleaned-input (input-cleanup-proc input)])
  82. (cond
  83. ;; ... and check whether it satisfies the predicate
  84. [(pred cleaned-input)
  85. ;; if possible-answers are specified check,
  86. ;; whether the answer is a member of the
  87. ;; possible-answers
  88. (cond [possible-answers
  89. (cond [(member cleaned-input possible-answers) cleaned-input]
  90. ;; if the answer is not valid ...
  91. [else
  92. ;; ... output the invalid input
  93. ;; message ...
  94. (write-string invalid-input-message)
  95. ;; ... and ask the question again
  96. (try-again (ask-question))])]
  97. [else cleaned-input])]
  98. [else (write-string invalid-input-message)
  99. (try-again (ask-question))]))))
  100. (define ask-user-for-decision-with-continuations
  101. (λ (question choices choice-texts continuations)
  102. (define build-question-text
  103. (λ (question choices choice-texts)
  104. (call-with-output-string
  105. (λ (string-port)
  106. (simple-format string-port "~a\n" question)
  107. (let next-choice ([rest-choices choices] [rest-choice-texts choice-texts])
  108. (cond
  109. [(null? rest-choices)
  110. (simple-format string-port "")]
  111. [else
  112. (simple-format string-port "~a: ~a\n" (first rest-choices) (first rest-choice-texts))
  113. (next-choice (cdr rest-choices)
  114. (cdr rest-choice-texts))]))))))
  115. (let ([choi (ask-user (build-question-text question choices choice-texts)
  116. (λ (input)
  117. (member input choices))
  118. #:possible-answers choices
  119. #:question-to-choices-separator "")])
  120. (let next ([rest-choices choices] [rest-continuations continuations])
  121. (cond
  122. [(null? rest-choices)
  123. (error "one of the choices should have been equal" choi choices)]
  124. [(string=? choi (first rest-choices))
  125. ((first rest-continuations))]
  126. [else
  127. (next (cdr rest-choices)
  128. (cdr rest-continuations))])))))
  129. (define ask-user-for-text
  130. (λ (question)
  131. (ask-user question (λ (input) #t))))
  132. (define ask-user-for-character
  133. (lambda* (question
  134. char-pred
  135. #:key
  136. (invalid-input-message "Invalid input. Enter a character.\n"))
  137. (ask-user question
  138. (λ (input)
  139. (and (= (string-length input) 1)
  140. (char-pred (car (string->list input)))))
  141. #:invalid-input-message invalid-input-message)))
  142. (define ask-user-for-number
  143. (λ (question number-pred)
  144. "Ask the user for a input, which must be a number and
  145. secondly must satisfy the given number predicate."
  146. (string->number
  147. (ask-user question
  148. (lambda (input)
  149. (and (string->number input)
  150. (number-pred (string->number input))))))))
  151. (define ask-user-for-integer-number
  152. (λ (question number-pred)
  153. (ask-user-for-number question (λ (num) (and (number-pred num)
  154. (integer? num))))))
  155. (define ask-user-for-yes-no-decision
  156. (λ (question positive-answers negative-answers)
  157. (let ([user-input
  158. (ask-user question
  159. (λ (input)
  160. (member input
  161. (lset-union string=? positive-answers negative-answers)))
  162. #:possible-answers (reverse (lset-union string=?
  163. positive-answers
  164. negative-answers)))])
  165. (member user-input positive-answers))))
  166. (define ask-user-for-decision
  167. (λ (question decisions)
  168. (ask-user question
  169. (λ (input)
  170. (member input decisions))
  171. #:possible-answers decisions)))
  172. (define confirm-info-message
  173. (λ (msg)
  174. (simple-format (current-output-port) "~a" msg)
  175. (read-line)))
  176. (define string-format
  177. (λ (format-string . args)
  178. (call-with-output-string
  179. (λ (string-port)
  180. (apply simple-format
  181. (cons string-port (cons format-string args)))))))