interact.scm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. (library (interact)
  2. (export interactive-loop
  3. interact-search
  4. interact-learn)
  5. (import (except (rnrs base)
  6. vector-for-each)
  7. (only (guile)
  8. lambda* λ
  9. simple-format
  10. current-output-port
  11. remainder
  12. member
  13. random
  14. pk)
  15. ;; GNU Guile batteries
  16. (ice-9 exceptions)
  17. (ice-9 match)
  18. ;; file system
  19. (fslib)
  20. ;; json
  21. (json)
  22. (json-reader)
  23. (json-writer)
  24. ;; custom libraries
  25. (vocabulary-data)
  26. (statistics)
  27. (print-utils)
  28. ;; custom helper libraries
  29. (list-procs)
  30. (alist-procs)
  31. (math)
  32. (search)
  33. (iter-utils)
  34. (print-utils)
  35. (bool-utils)
  36. ;; SRFIs
  37. (srfi srfi-1)
  38. ;; SRFI 8 - receive form
  39. (srfi srfi-8)
  40. ;; SRFI 43 - vector procs
  41. (srfi srfi-43)
  42. (vector-procs)
  43. ;; SRFI 69 - hash tables
  44. (srfi srfi-69)
  45. ;; other libs
  46. (user-input-output)
  47. (message-builder)))
  48. ;; ===========
  49. ;; INTERACTION
  50. ;; ===========
  51. (define interact-select-searched-attributes
  52. (λ (vocabulary)
  53. "Ask the user which attributes of a vocabulary entry
  54. they want to search in. Return a list of searched in
  55. attributes."
  56. (let* ([attribute-names (vocabulary-get-attribute-names vocabulary)]
  57. [additional-choices '("all" "metadata" "translation-data")]
  58. [all-choices (append additional-choices attribute-names)]
  59. [num-attributes (length attribute-names)]
  60. [num-additonal-choices (length additional-choices)]
  61. [additional-choices-and-values
  62. `((0 . ,attribute-names)
  63. (1 . ,(vocabulary-get-metadata-attribute-names vocabulary))
  64. (2 . ,(vocabulary-get-translation-data-attribute-names vocabulary)))]
  65. [num-all-choices
  66. (+ num-attributes
  67. num-additonal-choices)]
  68. [attribute-choices
  69. ;; All choices are returned as a list -- makes later processing
  70. ;; simpler.
  71. (map (λ (e1 e2) (cons e1 (list e2)))
  72. (range num-additonal-choices num-all-choices)
  73. attribute-names)]
  74. [answer->value-hash-table
  75. (alist->hash-table
  76. (append additional-choices-and-values attribute-choices))]
  77. [message
  78. (string-append "Which attribute do you want to search?"
  79. "\n"
  80. (choices->message (range 0 num-all-choices) all-choices))])
  81. (let ([choice
  82. (ask-user-for-decision-return-value
  83. "Which attribute do you want to search?"
  84. (map number->string (range 0 num-all-choices))
  85. all-choices
  86. `(,attribute-names
  87. ,(vocabulary-get-metadata-attribute-names vocabulary)
  88. ,(vocabulary-get-translation-data-attribute-names vocabulary)
  89. ,@attribute-names)
  90. #:prompt-text "choose")])
  91. (simple-format (current-output-port) "~a\n" choice)
  92. (if (pair? choice) choice (cons choice '()))))))
  93. (define interact-choose-number-comparison
  94. (lambda* (attribute-name)
  95. "Query the user for a number comparison."
  96. (define make-compare-as-numbers-otherwise-false
  97. (λ (compare-func)
  98. (λ (attr-val raw-input-value)
  99. (let ([input-value (string->number raw-input-value)])
  100. (if input-value
  101. (compare-func attr-val input-value)
  102. #f)))))
  103. (define choices-with-texts
  104. `(("<" . "less than")
  105. (">" . "greater than")
  106. ("<=" . "less than or equal")
  107. (">=" . "greater than or equal")
  108. ("=" . "equal")
  109. ("!=" . "not equal")))
  110. (ask-user-for-decision-return-value
  111. (simple-format #f
  112. "How do you want to compare the searched value with attribute ~a?"
  113. attribute-name)
  114. (map (λ (el) (car el)) choices-with-texts)
  115. (map (λ (el) (cdr el)) choices-with-texts)
  116. (list (make-compare-as-numbers-otherwise-false
  117. (λ (attr-val input-value)
  118. (< attr-val input-value)))
  119. (make-compare-as-numbers-otherwise-false
  120. (λ (attr-val input-value)
  121. (> attr-val input-value)))
  122. (make-compare-as-numbers-otherwise-false
  123. (λ (attr-val input-value)
  124. (<= attr-val input-value)))
  125. (make-compare-as-numbers-otherwise-false
  126. (λ (attr-val input-value)
  127. (>= attr-val input-value)))
  128. (make-compare-as-numbers-otherwise-false
  129. (λ (attr-val input-value)
  130. (= attr-val input-value)))
  131. (make-compare-as-numbers-otherwise-false
  132. (λ (attr-val input-value)
  133. (not (= attr-val input-value))))))))
  134. (define attribute-lookup
  135. (λ (vocabulary-entry attribute-name)
  136. ;; look for the attribute in the metadata
  137. (alist-refs vocabulary-entry
  138. (cons "metadata" (list attribute-name))
  139. ;; if the attribute is not in the
  140. ;; metadata look for it in the
  141. ;; translation-data
  142. #:default-thunk
  143. (λ ()
  144. (alist-refs vocabulary-entry
  145. (cons "translation-data" (list attribute-name))
  146. #:default-thunk (λ () 'not-found))))))
  147. (define interact-general-comparator
  148. (λ (vocabulary searched-attributes)
  149. "Query the user for ways in which attributes shall be
  150. compared to the search term."
  151. ;; The first entry in the vocabulary is considered to be
  152. ;; somewhat special, as it is assumed to have the
  153. ;; structure, which all of the vocabulary entries are
  154. ;; expected to have and it is used as a guideline for
  155. ;; operations, which require knowledge about types.
  156. (let ([voc-entry (get:vocabulary/nth-entry vocabulary 0)])
  157. ;; TODO: find a good way to specify multiple
  158. ;; comparators for comparisons, which work with
  159. ;; the same type (for example numbers) but the
  160. ;; user wants them to work differently.
  161. (let next-attribute
  162. ([remaining-attributes searched-attributes]
  163. [string-comparator #f]
  164. [number-comparator #f]
  165. [boolean-comparator #f]
  166. [vector-comparator #f])
  167. (cond
  168. [(null? remaining-attributes)
  169. (make-general-comparator #:number-comparator number-comparator
  170. #:string-comparator string-comparator
  171. #:boolean-comparator boolean-comparator
  172. #:vector-comparator vector-comparator)]
  173. [else
  174. (cond
  175. [(and number-comparator
  176. string-comparator
  177. boolean-comparator
  178. vector-comparator)
  179. (make-general-comparator #:number-comparator number-comparator
  180. #:string-comparator string-comparator
  181. #:boolean-comparator boolean-comparator
  182. #:vector-comparator vector-comparator)]
  183. [else
  184. (let* ([attr-name (first remaining-attributes)]
  185. [attr-val (attribute-lookup voc-entry attr-name)])
  186. (cond
  187. [(number? attr-val)
  188. (next-attribute (cdr remaining-attributes)
  189. string-comparator
  190. (interact-choose-number-comparison attr-name)
  191. boolean-comparator
  192. vector-comparator)]
  193. [else
  194. (next-attribute (cdr remaining-attributes)
  195. string-comparator
  196. number-comparator
  197. boolean-comparator
  198. vector-comparator)]))])])))))
  199. (define interactive-new-search
  200. (lambda* (vocabulary #:key (negated #f))
  201. "Search vocabulary interactively, asking the user what
  202. attributes they would like to search for a search term."
  203. (let ([metadata (get:vocabulary/metadata vocabulary)]
  204. [searched-attributes (interact-select-searched-attributes vocabulary)])
  205. (define attribute-test
  206. (λ (attr)
  207. (member (get:attribute/key attr)
  208. searched-attributes)))
  209. (define search-result
  210. (vocabulary-search vocabulary
  211. (ask-user-for-text "" #:prompt-text "search term")
  212. #:negated negated
  213. #:equal-test?
  214. (interact-general-comparator vocabulary searched-attributes)
  215. #:attribute-test?
  216. (λ (attr)
  217. (member (alist-item-key attr)
  218. searched-attributes))))
  219. (values 'search-result
  220. `(("metadata" . ,metadata)
  221. ("words" . ,search-result))))))
  222. (define interact-search
  223. (lambda* (vocabulary #:key (search-result-vocabulary #f))
  224. "Search vocabulary interactively."
  225. (define choices-with-texts
  226. `(("s" . "new search")
  227. ("n" . "narrow search results")
  228. ("!n" . "negated narrow search results")
  229. ("w" . "widen search results")
  230. ("!w" . "negated widen search results")
  231. ("s0" . "reset search results")
  232. ("i" . "show search result info")
  233. ("e" . "exit")))
  234. (let loop ()
  235. (ask-user-for-decision-with-continuations
  236. "How do you want to search?"
  237. (map (λ (el) (car el)) choices-with-texts)
  238. (map (λ (el) (cdr el)) choices-with-texts)
  239. (list (λ ()
  240. (let-values ([(tag data) (interactive-new-search vocabulary)])
  241. (interact-search vocabulary
  242. #:search-result-vocabulary
  243. data)))
  244. (λ ()
  245. (let-values ([(tag data) (interactive-new-search search-result-vocabulary)])
  246. (interact-search vocabulary
  247. #:search-result-vocabulary
  248. data)))
  249. (λ ()
  250. (let-values ([(tag data)
  251. (interactive-new-search search-result-vocabulary #:negated #t)])
  252. (interact-search vocabulary
  253. #:search-result-vocabulary
  254. data)))
  255. (λ ()
  256. (let-values ([(tag data) (interactive-new-search vocabulary)])
  257. (interact-search vocabulary
  258. #:search-result-vocabulary
  259. (vocabulary-union search-result-vocabulary data))))
  260. (λ ()
  261. (let-values ([(tag data) (interactive-new-search vocabulary #:negated #t)])
  262. (interact-search vocabulary
  263. #:search-result-vocabulary
  264. (vocabulary-union search-result-vocabulary data))))
  265. (λ ()
  266. (interact-search vocabulary))
  267. (λ ()
  268. (vector-for-each (λ (ind res)
  269. (display-voc-entry res #:separator "---\n"))
  270. (get:vocabulary/entries
  271. (if search-result-vocabulary
  272. search-result-vocabulary
  273. vocabulary)))
  274. (loop))
  275. (λ ()
  276. (print-limited search-result-vocabulary)
  277. (if search-result-vocabulary
  278. (values 'search-result search-result-vocabulary)
  279. (values 'vocabulary vocabulary))))))))
  280. (define default-learn-config
  281. (alist-refs (get-json-from-file (fsing-join "settings.json"))
  282. '("learn")))
  283. ;; TODO: Implement a way to return the whole vocabulary
  284. ;; changed to the main interactive loop. One possible way to
  285. ;; achieve this is, to only pass a list of indices
  286. ;; accompanying the full vocabulary, instead of passing a
  287. ;; filtered vocabulary, so that using the indices entries of
  288. ;; the full vocabulary can be modified (mutated) and then
  289. ;; the full vocabulary can be returned.
  290. (define interact-learn
  291. (lambda* (vocabulary #:key (config default-learn-config))
  292. (define question "What do you want to do?")
  293. (define choices-texts-actions
  294. `(("n" "next word"
  295. ,(λ (voc-entries index)
  296. (learn-loop voc-entries
  297. (next-in-circle index (vector-length voc-entries)))))
  298. ("p" "previous word"
  299. ,(λ (voc-entries index)
  300. (learn-loop voc-entries
  301. (previous-in-circle index (vector-length voc-entries)))))
  302. ("sh" "shuffle words"
  303. ,(λ (voc-entries index)
  304. (learn-loop (vector-shuffle voc-entries) 0)))
  305. ("i" "show word info"
  306. ,(λ (voc-entries index)
  307. (display-voc-entry (vector-ref voc-entries index)
  308. #:meta-attr-visibility-pred
  309. (λ (attr-name) #t)
  310. #:translation-attr-visibility-pred
  311. (λ (attr-name) #t))
  312. (learn-loop voc-entries (remainder index (vector-length voc-entries)))))
  313. ("ml" "mark word learned"
  314. ,(λ (voc-entries index)
  315. (entries:set-learned-status! voc-entries
  316. (list index)
  317. (list #t))
  318. (learn-loop voc-entries
  319. (next-in-circle index
  320. (vector-length voc-entries)))))
  321. ("mnl" "mark word not learned"
  322. ,(λ (voc-entries index)
  323. (entries:set-learned-status! voc-entries
  324. (list index)
  325. (list #f))
  326. (learn-loop voc-entries
  327. (next-in-circle index
  328. (vector-length voc-entries)))))
  329. ("cd" "change difficulty"
  330. ,(λ (voc-entries index)
  331. (simple-format (current-output-port) "change difficulty - not yet implemented\n")
  332. (learn-loop voc-entries (remainder index (vector-length voc-entries)))))
  333. ("cr" "change relevance"
  334. ,(λ (voc-entries index)
  335. (simple-format (current-output-port) "change relevance - not yet implemented\n")
  336. (learn-loop voc-entries (remainder index (vector-length voc-entries)))))
  337. ("e" "exit learning"
  338. ,(λ (voc-entries index)
  339. (values 'search-result vocabulary)))
  340. ("es" "exit learning saving changes"
  341. ,(λ (voc-entries index)
  342. ;; TODO: return filtered or not filtered
  343. ;; (symbol), depending on how learn was started
  344. (values 'update
  345. `(("metadata" . ,(get:vocabulary/metadata vocabulary))
  346. ("words" . ,voc-entries)))))))
  347. (define meta-attr-visibility-pred
  348. (λ (attr-name)
  349. (let ([hidden-attrs
  350. (alist-refs default-learn-config
  351. '("hidden-attributes" "metadata"))])
  352. (not (vector-contains hidden-attrs attr-name)))))
  353. (define translation-attr-visibility-pred
  354. (λ (attr-name)
  355. (let ([hidden-attrs
  356. (alist-refs default-learn-config
  357. '("hidden-attributes" "translation-data"))])
  358. (not (vector-contains hidden-attrs attr-name)))))
  359. (define learn-loop
  360. (λ (voc-entries index)
  361. (let ([voc-entry (vector-ref voc-entries index)])
  362. (display-voc-entry voc-entry
  363. #:meta-attr-visibility-pred
  364. meta-attr-visibility-pred
  365. #:translation-attr-visibility-pred
  366. translation-attr-visibility-pred))
  367. (ask-user-for-decision-with-continuations
  368. question
  369. (map (λ (el) (first el)) choices-texts-actions)
  370. (map (λ (el) (second el)) choices-texts-actions)
  371. (map (λ (el)
  372. (λ ()
  373. ((third el) voc-entries index)))
  374. choices-texts-actions))))
  375. (cond
  376. [(> (vocabulary/entries:length vocabulary) 0)
  377. (learn-loop (get:vocabulary/entries vocabulary) 0)]
  378. [else
  379. (confirm-info-message "empty vocabulary subset")
  380. (values 'search-result vocabulary)])))
  381. (define interact-persist
  382. (λ (vocabulary settings)
  383. (let ([file-location
  384. (hash-table-ref/default settings
  385. "vocabulary"
  386. "default-vocabulary.json")])
  387. (save-vocabulary file-location vocabulary))
  388. (values 'continue vocabulary)))
  389. (define interact-reload
  390. (λ (settings)
  391. (values 'continue
  392. (read-vocabulary
  393. (hash-table-ref/default settings
  394. "vocabulary"
  395. "default-vocabulary.json")))))
  396. (define interactive-loop
  397. (lambda* (vocabulary settings #:key (search-result-vocabulary #f))
  398. (define choices-texts-actions
  399. `(("s" "search"
  400. ,(λ ()
  401. (interact-search vocabulary
  402. #:search-result-vocabulary
  403. search-result-vocabulary)))
  404. ("l" "learn"
  405. ,(λ () (interact-learn (or search-result-vocabulary vocabulary))))
  406. ("p" "persist data"
  407. ,(λ () (interact-persist vocabulary settings)))
  408. ("r" "reload data"
  409. ,(λ () (interact-reload settings)))
  410. ("stat" "show statistics"
  411. ,(λ ()
  412. (display-statistics (or search-result-vocabulary vocabulary))
  413. (cond
  414. [search-result-vocabulary
  415. (values 'search-result search-result-vocabulary)]
  416. [else
  417. (values 'vocabulary vocabulary)])))
  418. ("e" "exit"
  419. ,(λ () (values 'exit 'none)))))
  420. (define loop
  421. (lambda* ()
  422. (let-values ([(tag data)
  423. (ask-user-for-decision-with-continuations
  424. "What do you want to do?"
  425. (map (λ (elem) (first elem)) choices-texts-actions)
  426. (map (λ (elem) (second elem)) choices-texts-actions)
  427. (map (λ (elem) (third elem)) choices-texts-actions))])
  428. (cond
  429. [(eq? tag 'search-result)
  430. #;(vector-for-each (λ (ind res) (display-voc-entry res #:separator "---\n"))
  431. (get:vocabulary/entries data))
  432. (interactive-loop vocabulary settings #:search-result-vocabulary data)]
  433. [(eq? tag 'vocabulary)
  434. (interactive-loop vocabulary settings #:search-result-vocabulary #f)]
  435. [(eq? tag 'continue)
  436. (interactive-loop vocabulary settings #:search-result-vocabulary #f)]
  437. [(eq? tag 'update)
  438. (simple-format (current-output-port) "~a\n" "updating vocabulary")
  439. (interactive-loop (vocabulary-merge vocabulary data)
  440. settings
  441. #:search-result-vocabulary data)]
  442. [(eq? tag 'exit)
  443. 'exit]
  444. [else
  445. (raise-exception
  446. (make-exception
  447. (make-non-continuable-error)
  448. (make-exception-with-message
  449. "unexpected return value to interactive main loop")
  450. (make-exception-with-irritants data)
  451. (make-exception-with-origin 'interactive-loop)))]))))
  452. ;; start the interaction loop, with unfiltered vocabulary
  453. (loop)))