rkoans 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (460) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/CURRICULUM.md +86 -0
  4. data/README.md +77 -0
  5. data/THIRD_PARTY_NOTICES.md +30 -0
  6. data/exercises/about-array-assignment/about_array_assignment.rb +45 -0
  7. data/exercises/about-array-assignment/brief.md +11 -0
  8. data/exercises/about-array-assignment/hint.md +11 -0
  9. data/exercises/about-arrays/about_arrays.rb +77 -0
  10. data/exercises/about-arrays/brief.md +16 -0
  11. data/exercises/about-arrays/hint.md +13 -0
  12. data/exercises/about-asserts/about_asserts.rb +22 -0
  13. data/exercises/about-asserts/brief.md +22 -0
  14. data/exercises/about-asserts/hint.md +10 -0
  15. data/exercises/about-blocks/about_blocks.rb +76 -0
  16. data/exercises/about-blocks/brief.md +44 -0
  17. data/exercises/about-blocks/hint.md +7 -0
  18. data/exercises/about-class-methods/about_class_methods.rb +105 -0
  19. data/exercises/about-class-methods/brief.md +73 -0
  20. data/exercises/about-class-methods/hint.md +8 -0
  21. data/exercises/about-classes/about_classes.rb +131 -0
  22. data/exercises/about-classes/brief.md +73 -0
  23. data/exercises/about-classes/hint.md +7 -0
  24. data/exercises/about-constants/about_constants.rb +49 -0
  25. data/exercises/about-constants/brief.md +51 -0
  26. data/exercises/about-constants/hint.md +7 -0
  27. data/exercises/about-control-statements/about_control_statements.rb +152 -0
  28. data/exercises/about-control-statements/brief.md +9 -0
  29. data/exercises/about-control-statements/hint.md +7 -0
  30. data/exercises/about-dice-project/about_dice_project.rb +27 -0
  31. data/exercises/about-dice-project/brief.md +12 -0
  32. data/exercises/about-dice-project/hint.md +5 -0
  33. data/exercises/about-exceptions/about_exceptions.rb +84 -0
  34. data/exercises/about-exceptions/brief.md +19 -0
  35. data/exercises/about-exceptions/hint.md +8 -0
  36. data/exercises/about-extra-credit/about_extra_credit.rb +117 -0
  37. data/exercises/about-extra-credit/brief.md +28 -0
  38. data/exercises/about-extra-credit/hint.md +5 -0
  39. data/exercises/about-hashes/about_hashes.rb +160 -0
  40. data/exercises/about-hashes/brief.md +9 -0
  41. data/exercises/about-hashes/hint.md +8 -0
  42. data/exercises/about-inheritance/about_inheritance.rb +63 -0
  43. data/exercises/about-inheritance/brief.md +50 -0
  44. data/exercises/about-inheritance/hint.md +8 -0
  45. data/exercises/about-iteration/about_iteration.rb +96 -0
  46. data/exercises/about-iteration/brief.md +26 -0
  47. data/exercises/about-iteration/hint.md +6 -0
  48. data/exercises/about-keyword-arguments/about_keyword_arguments.rb +56 -0
  49. data/exercises/about-keyword-arguments/brief.md +28 -0
  50. data/exercises/about-keyword-arguments/hint.md +7 -0
  51. data/exercises/about-message-passing/about_message_passing.rb +112 -0
  52. data/exercises/about-message-passing/brief.md +59 -0
  53. data/exercises/about-message-passing/hint.md +8 -0
  54. data/exercises/about-methods/about_methods.rb +104 -0
  55. data/exercises/about-methods/brief.md +55 -0
  56. data/exercises/about-methods/hint.md +8 -0
  57. data/exercises/about-modules/about_modules.rb +43 -0
  58. data/exercises/about-modules/brief.md +44 -0
  59. data/exercises/about-modules/hint.md +8 -0
  60. data/exercises/about-nil/about_nil.rb +29 -0
  61. data/exercises/about-nil/brief.md +12 -0
  62. data/exercises/about-nil/hint.md +11 -0
  63. data/exercises/about-objects/about_objects.rb +51 -0
  64. data/exercises/about-objects/brief.md +14 -0
  65. data/exercises/about-objects/hint.md +12 -0
  66. data/exercises/about-open-classes/about_open_classes.rb +38 -0
  67. data/exercises/about-open-classes/brief.md +34 -0
  68. data/exercises/about-open-classes/hint.md +7 -0
  69. data/exercises/about-pattern-matching/about_pattern_matching.rb +106 -0
  70. data/exercises/about-pattern-matching/brief.md +124 -0
  71. data/exercises/about-pattern-matching/hint.md +8 -0
  72. data/exercises/about-proxy-object-project/about_proxy_object_project.rb +43 -0
  73. data/exercises/about-proxy-object-project/brief.md +10 -0
  74. data/exercises/about-proxy-object-project/hint.md +5 -0
  75. data/exercises/about-regular-expressions/about_regular_expressions.rb +174 -0
  76. data/exercises/about-regular-expressions/brief.md +9 -0
  77. data/exercises/about-regular-expressions/hint.md +8 -0
  78. data/exercises/about-sandwich-code/about_sandwich_code.rb +58 -0
  79. data/exercises/about-sandwich-code/brief.md +14 -0
  80. data/exercises/about-sandwich-code/hint.md +5 -0
  81. data/exercises/about-scope/about_scope.rb +76 -0
  82. data/exercises/about-scope/brief.md +35 -0
  83. data/exercises/about-scope/hint.md +8 -0
  84. data/exercises/about-scoring-project/about_scoring_project.rb +16 -0
  85. data/exercises/about-scoring-project/brief.md +10 -0
  86. data/exercises/about-scoring-project/hint.md +5 -0
  87. data/exercises/about-strings/about_strings.rb +142 -0
  88. data/exercises/about-strings/brief.md +22 -0
  89. data/exercises/about-strings/hint.md +11 -0
  90. data/exercises/about-symbols/about_symbols.rb +53 -0
  91. data/exercises/about-symbols/brief.md +21 -0
  92. data/exercises/about-symbols/hint.md +12 -0
  93. data/exercises/about-to-str/about_to_str.rb +49 -0
  94. data/exercises/about-to-str/brief.md +37 -0
  95. data/exercises/about-to-str/hint.md +7 -0
  96. data/exercises/about-triangle-project/about_triangle_project.rb +15 -0
  97. data/exercises/about-triangle-project/brief.md +11 -0
  98. data/exercises/about-triangle-project/hint.md +5 -0
  99. data/exercises/about-triangle-project-two/about_triangle_project_two.rb +22 -0
  100. data/exercises/about-triangle-project-two/brief.md +10 -0
  101. data/exercises/about-triangle-project-two/hint.md +5 -0
  102. data/exercises/about-true-and-false/about_true_and_false.rb +26 -0
  103. data/exercises/about-true-and-false/brief.md +22 -0
  104. data/exercises/about-true-and-false/hint.md +7 -0
  105. data/exercises/blocks-and-methods/blocks_and_methods.rb +14 -0
  106. data/exercises/blocks-and-methods/brief.md +8 -0
  107. data/exercises/blocks-and-methods/hint.md +6 -0
  108. data/exercises/catalog.json +61 -0
  109. data/exercises/class-state/brief.md +8 -0
  110. data/exercises/class-state/class_state.rb +39 -0
  111. data/exercises/class-state/hint.md +5 -0
  112. data/exercises/closure-capture/brief.md +8 -0
  113. data/exercises/closure-capture/closure_capture.rb +21 -0
  114. data/exercises/closure-capture/hint.md +5 -0
  115. data/exercises/collection-basics/brief.md +8 -0
  116. data/exercises/collection-basics/collection_basics.rb +16 -0
  117. data/exercises/collection-basics/hint.md +6 -0
  118. data/exercises/constant-scope/brief.md +8 -0
  119. data/exercises/constant-scope/constant_scope.rb +18 -0
  120. data/exercises/constant-scope/hint.md +5 -0
  121. data/exercises/core-protocols/brief.md +8 -0
  122. data/exercises/core-protocols/core_protocols.rb +43 -0
  123. data/exercises/core-protocols/hint.md +5 -0
  124. data/exercises/delegation/brief.md +8 -0
  125. data/exercises/delegation/delegation.rb +20 -0
  126. data/exercises/delegation/hint.md +5 -0
  127. data/exercises/dynamic-methods/brief.md +8 -0
  128. data/exercises/dynamic-methods/dynamic_methods.rb +33 -0
  129. data/exercises/dynamic-methods/hint.md +5 -0
  130. data/exercises/exception-flow/brief.md +8 -0
  131. data/exercises/exception-flow/exception_flow.rb +27 -0
  132. data/exercises/exception-flow/hint.md +5 -0
  133. data/exercises/heredoc-layout/brief.md +14 -0
  134. data/exercises/heredoc-layout/heredoc_layout.rb +25 -0
  135. data/exercises/heredoc-layout/hint.md +9 -0
  136. data/exercises/keyword-forwarding/brief.md +8 -0
  137. data/exercises/keyword-forwarding/hint.md +5 -0
  138. data/exercises/keyword-forwarding/keyword_forwarding.rb +27 -0
  139. data/exercises/lazy-enumeration/brief.md +8 -0
  140. data/exercises/lazy-enumeration/hint.md +5 -0
  141. data/exercises/lazy-enumeration/lazy_enumeration.rb +16 -0
  142. data/exercises/literals-and-operators/brief.md +20 -0
  143. data/exercises/literals-and-operators/hint.md +5 -0
  144. data/exercises/literals-and-operators/literals_and_operators.rb +15 -0
  145. data/exercises/method-contracts/brief.md +8 -0
  146. data/exercises/method-contracts/hint.md +5 -0
  147. data/exercises/method-contracts/method_contracts.rb +22 -0
  148. data/exercises/method-lookup/brief.md +8 -0
  149. data/exercises/method-lookup/hint.md +5 -0
  150. data/exercises/method-lookup/method_lookup.rb +26 -0
  151. data/exercises/nonlocal-exit/brief.md +8 -0
  152. data/exercises/nonlocal-exit/hint.md +5 -0
  153. data/exercises/nonlocal-exit/nonlocal_exit.rb +29 -0
  154. data/exercises/numbered-parameters/brief.md +18 -0
  155. data/exercises/numbered-parameters/hint.md +5 -0
  156. data/exercises/numbered-parameters/numbered_parameters.rb +18 -0
  157. data/exercises/numeric-precision/brief.md +8 -0
  158. data/exercises/numeric-precision/hint.md +5 -0
  159. data/exercises/numeric-precision/numeric_precision.rb +17 -0
  160. data/exercises/pattern-matching/brief.md +8 -0
  161. data/exercises/pattern-matching/hint.md +5 -0
  162. data/exercises/pattern-matching/pattern_matching.rb +22 -0
  163. data/exercises/refinement-scope/brief.md +8 -0
  164. data/exercises/refinement-scope/hint.md +5 -0
  165. data/exercises/refinement-scope/refinement_scope.rb +33 -0
  166. data/exercises/regexp-captures/brief.md +8 -0
  167. data/exercises/regexp-captures/hint.md +5 -0
  168. data/exercises/regexp-captures/regexp_captures.rb +25 -0
  169. data/exercises/runtime-context/brief.md +20 -0
  170. data/exercises/runtime-context/hint.md +8 -0
  171. data/exercises/runtime-context/runtime_context.rb +11 -0
  172. data/exercises/shared-rows/brief.md +8 -0
  173. data/exercises/shared-rows/hint.md +7 -0
  174. data/exercises/shared-rows/shared_rows.rb +13 -0
  175. data/exercises/singleton-pattern/brief.md +8 -0
  176. data/exercises/singleton-pattern/hint.md +5 -0
  177. data/exercises/singleton-pattern/singleton_pattern.rb +20 -0
  178. data/exercises/time-and-date/brief.md +8 -0
  179. data/exercises/time-and-date/hint.md +5 -0
  180. data/exercises/time-and-date/time_and_date.rb +21 -0
  181. data/exercises/visibility/brief.md +8 -0
  182. data/exercises/visibility/hint.md +5 -0
  183. data/exercises/visibility/visibility.rb +32 -0
  184. data/lib/rkoans/catalog.rb +31 -0
  185. data/lib/rkoans/checker.rb +62 -0
  186. data/lib/rkoans/cli.rb +79 -0
  187. data/lib/rkoans/error.rb +6 -0
  188. data/lib/rkoans/exercise.rb +50 -0
  189. data/lib/rkoans/keyboard.rb +51 -0
  190. data/lib/rkoans/progress.rb +39 -0
  191. data/lib/rkoans/selection.rb +31 -0
  192. data/lib/rkoans/session.rb +127 -0
  193. data/lib/rkoans/terminal_view.rb +99 -0
  194. data/lib/rkoans/version.rb +1 -1
  195. data/lib/rkoans/watcher.rb +58 -0
  196. data/lib/rkoans.rb +13 -3
  197. data/sig/about_array_assignment_acceptance_test.rbs +3 -0
  198. data/sig/about_arrays_acceptance_test.rbs +3 -0
  199. data/sig/about_asserts_command_test.rbs +4 -0
  200. data/sig/about_blocks_acceptance_test.rbs +3 -0
  201. data/sig/about_class_methods_acceptance_test.rbs +3 -0
  202. data/sig/about_classes_acceptance_test.rbs +3 -0
  203. data/sig/about_constants_acceptance_test.rbs +3 -0
  204. data/sig/about_control_statements_acceptance_test.rbs +3 -0
  205. data/sig/about_dice_project_acceptance_test.rbs +3 -0
  206. data/sig/about_exceptions_acceptance_test.rbs +3 -0
  207. data/sig/about_extra_credit_acceptance_test.rbs +5 -0
  208. data/sig/about_hashes_acceptance_test.rbs +3 -0
  209. data/sig/about_inheritance_acceptance_test.rbs +3 -0
  210. data/sig/about_iteration_acceptance_test.rbs +3 -0
  211. data/sig/about_keyword_arguments_acceptance_test.rbs +3 -0
  212. data/sig/about_message_passing_acceptance_test.rbs +3 -0
  213. data/sig/about_methods_acceptance_test.rbs +3 -0
  214. data/sig/about_modules_acceptance_test.rbs +3 -0
  215. data/sig/about_nil_acceptance_test.rbs +3 -0
  216. data/sig/about_objects_acceptance_test.rbs +3 -0
  217. data/sig/about_open_classes_acceptance_test.rbs +3 -0
  218. data/sig/about_pattern_matching_acceptance_test.rbs +3 -0
  219. data/sig/about_proxy_object_project_acceptance_test.rbs +3 -0
  220. data/sig/about_regular_expressions_acceptance_test.rbs +3 -0
  221. data/sig/about_sandwich_code_acceptance_test.rbs +3 -0
  222. data/sig/about_scope_acceptance_test.rbs +3 -0
  223. data/sig/about_scoring_project_acceptance_test.rbs +3 -0
  224. data/sig/about_strings_acceptance_test.rbs +3 -0
  225. data/sig/about_symbols_acceptance_test.rbs +3 -0
  226. data/sig/about_to_str_acceptance_test.rbs +3 -0
  227. data/sig/about_triangle_project_acceptance_test.rbs +3 -0
  228. data/sig/about_triangle_project_two_acceptance_test.rbs +3 -0
  229. data/sig/about_true_and_false_command_test.rbs +4 -0
  230. data/sig/catalog_error_command_test.rbs +4 -0
  231. data/sig/checker_test.rbs +9 -0
  232. data/sig/class_state_acceptance_test.rbs +3 -0
  233. data/sig/closure_capture_acceptance_test.rbs +3 -0
  234. data/sig/command_support.rbs +18 -0
  235. data/sig/command_test.rbs +5 -0
  236. data/sig/constant_scope_acceptance_test.rbs +3 -0
  237. data/sig/core_protocols_acceptance_test.rbs +3 -0
  238. data/sig/curriculum_cop_test.rbs +9 -0
  239. data/sig/delegation_acceptance_test.rbs +3 -0
  240. data/sig/dynamic_methods_acceptance_test.rbs +3 -0
  241. data/sig/exception_flow_acceptance_test.rbs +3 -0
  242. data/sig/exercise_test.rbs +3 -0
  243. data/sig/exercises/about_array_assignment.rbs +36 -0
  244. data/sig/exercises/about_arrays.rbs +51 -0
  245. data/sig/exercises/about_asserts.rbs +23 -0
  246. data/sig/exercises/about_blocks.rbs +40 -0
  247. data/sig/exercises/about_class_methods.rbs +62 -0
  248. data/sig/exercises/about_classes.rbs +88 -0
  249. data/sig/exercises/about_constants.rbs +53 -0
  250. data/sig/exercises/about_control_statements.rbs +46 -0
  251. data/sig/exercises/about_dice_project.rbs +16 -0
  252. data/sig/exercises/about_exceptions.rbs +36 -0
  253. data/sig/exercises/about_extra_credit.rbs +36 -0
  254. data/sig/exercises/about_hashes.rbs +62 -0
  255. data/sig/exercises/about_inheritance.rbs +50 -0
  256. data/sig/exercises/about_iteration.rbs +37 -0
  257. data/sig/exercises/about_keyword_arguments.rbs +33 -0
  258. data/sig/exercises/about_message_passing.rbs +68 -0
  259. data/sig/exercises/about_methods.rbs +68 -0
  260. data/sig/exercises/about_modules.rbs +38 -0
  261. data/sig/exercises/about_nil.rbs +23 -0
  262. data/sig/exercises/about_objects.rbs +38 -0
  263. data/sig/exercises/about_open_classes.rbs +31 -0
  264. data/sig/exercises/about_pattern_matching.rbs +65 -0
  265. data/sig/exercises/about_proxy_object_project.rbs +31 -0
  266. data/sig/exercises/about_regular_expressions.rbs +83 -0
  267. data/sig/exercises/about_sandwich_code.rbs +17 -0
  268. data/sig/exercises/about_scope.rbs +59 -0
  269. data/sig/exercises/about_scoring_project.rbs +16 -0
  270. data/sig/exercises/about_strings.rbs +85 -0
  271. data/sig/exercises/about_symbols.rbs +46 -0
  272. data/sig/exercises/about_to_str.rbs +36 -0
  273. data/sig/exercises/about_triangle_project.rbs +11 -0
  274. data/sig/exercises/about_triangle_project_two.rbs +12 -0
  275. data/sig/exercises/about_true_and_false.rbs +27 -0
  276. data/sig/exercises/blocks_and_methods.rbs +11 -0
  277. data/sig/exercises/class_state.rbs +19 -0
  278. data/sig/exercises/closure_capture.rbs +10 -0
  279. data/sig/exercises/collection_basics.rbs +6 -0
  280. data/sig/exercises/collection_basics_test.rbs +4 -0
  281. data/sig/exercises/constant_scope.rbs +11 -0
  282. data/sig/exercises/core_protocols.rbs +20 -0
  283. data/sig/exercises/delegation.rbs +14 -0
  284. data/sig/exercises/dynamic_methods.rbs +13 -0
  285. data/sig/exercises/exception_flow.rbs +11 -0
  286. data/sig/exercises/heredoc_layout.rbs +11 -0
  287. data/sig/exercises/keyword_forwarding.rbs +12 -0
  288. data/sig/exercises/lazy_enumeration.rbs +10 -0
  289. data/sig/exercises/literals_and_operators.rbs +12 -0
  290. data/sig/exercises/method_contracts.rbs +11 -0
  291. data/sig/exercises/method_lookup.rbs +16 -0
  292. data/sig/exercises/nonlocal_exit.rbs +14 -0
  293. data/sig/exercises/numbered_parameters.rbs +11 -0
  294. data/sig/exercises/numeric_precision.rbs +11 -0
  295. data/sig/exercises/pattern_matching.rbs +10 -0
  296. data/sig/exercises/refinement_scope.rbs +18 -0
  297. data/sig/exercises/regexp_captures.rbs +11 -0
  298. data/sig/exercises/runtime_context.rbs +23 -0
  299. data/sig/exercises/shared_rows.rbs +11 -0
  300. data/sig/exercises/singleton_pattern.rbs +14 -0
  301. data/sig/exercises/time_and_date.rbs +11 -0
  302. data/sig/exercises/visibility.rbs +18 -0
  303. data/sig/fixture_support.rbs +11 -0
  304. data/sig/help_hint_command_test.rbs +8 -0
  305. data/sig/heredoc_layout_acceptance_test.rbs +3 -0
  306. data/sig/keyboard_session_test.rbs +9 -0
  307. data/sig/keyword_forwarding_acceptance_test.rbs +3 -0
  308. data/sig/lazy_enumeration_acceptance_test.rbs +3 -0
  309. data/sig/literals_and_operators_acceptance_test.rbs +3 -0
  310. data/sig/method_contracts_acceptance_test.rbs +3 -0
  311. data/sig/method_lookup_acceptance_test.rbs +3 -0
  312. data/sig/nonlocal_exit_acceptance_test.rbs +3 -0
  313. data/sig/numbered_parameters_acceptance_test.rbs +3 -0
  314. data/sig/numeric_precision_acceptance_test.rbs +4 -0
  315. data/sig/packaging_test.rbs +3 -0
  316. data/sig/pattern_matching_acceptance_test.rbs +3 -0
  317. data/sig/reference_solutions_test.rbs +3 -0
  318. data/sig/refinement_scope_acceptance_test.rbs +3 -0
  319. data/sig/regexp_captures_acceptance_test.rbs +4 -0
  320. data/sig/rkoans/catalog.rbs +11 -0
  321. data/sig/rkoans/checker.rbs +22 -0
  322. data/sig/rkoans/cli.rbs +21 -0
  323. data/sig/rkoans/exercise.rbs +18 -0
  324. data/sig/rkoans/keyboard.rbs +13 -0
  325. data/sig/rkoans/progress.rbs +9 -0
  326. data/sig/rkoans/selection.rbs +9 -0
  327. data/sig/rkoans/session.rbs +30 -0
  328. data/sig/rkoans/terminal_view.rbs +24 -0
  329. data/sig/rkoans/watcher.rbs +18 -0
  330. data/sig/rkoans.rbs +2 -0
  331. data/sig/runtime_context_command_test.rbs +4 -0
  332. data/sig/selection_command_test.rbs +5 -0
  333. data/sig/session_command_test.rbs +12 -0
  334. data/sig/session_edge_cases_test.rbs +4 -0
  335. data/sig/shared_rows_acceptance_test.rbs +3 -0
  336. data/sig/singleton_pattern_acceptance_test.rbs +3 -0
  337. data/sig/time_and_date_acceptance_test.rbs +4 -0
  338. data/sig/visibility_acceptance_test.rbs +4 -0
  339. data/sig/watcher_test.rbs +3 -0
  340. data/solutions/about-array-assignment/about_array_assignment.rb +36 -0
  341. data/solutions/about-arrays/about_arrays.rb +53 -0
  342. data/solutions/about-asserts/about_asserts.rb +22 -0
  343. data/solutions/about-blocks/about_blocks.rb +30 -0
  344. data/solutions/about-class-methods/about_class_methods.rb +36 -0
  345. data/solutions/about-classes/about_classes.rb +40 -0
  346. data/solutions/about-constants/about_constants.rb +26 -0
  347. data/solutions/about-control-statements/about_control_statements.rb +35 -0
  348. data/solutions/about-dice-project/about_dice_project.rb +27 -0
  349. data/solutions/about-exceptions/about_exceptions.rb +30 -0
  350. data/solutions/about-extra-credit/about_extra_credit.rb +114 -0
  351. data/solutions/about-hashes/about_hashes.rb +52 -0
  352. data/solutions/about-inheritance/about_inheritance.rb +27 -0
  353. data/solutions/about-iteration/about_iteration.rb +31 -0
  354. data/solutions/about-keyword-arguments/about_keyword_arguments.rb +28 -0
  355. data/solutions/about-message-passing/about_message_passing.rb +38 -0
  356. data/solutions/about-methods/about_methods.rb +39 -0
  357. data/solutions/about-modules/about_modules.rb +23 -0
  358. data/solutions/about-nil/about_nil.rb +23 -0
  359. data/solutions/about-objects/about_objects.rb +34 -0
  360. data/solutions/about-open-classes/about_open_classes.rb +23 -0
  361. data/solutions/about-pattern-matching/about_pattern_matching.rb +39 -0
  362. data/solutions/about-proxy-object-project/about_proxy_object_project.rb +44 -0
  363. data/solutions/about-regular-expressions/about_regular_expressions.rb +60 -0
  364. data/solutions/about-sandwich-code/about_sandwich_code.rb +60 -0
  365. data/solutions/about-scope/about_scope.rb +33 -0
  366. data/solutions/about-scoring-project/about_scoring_project.rb +19 -0
  367. data/solutions/about-strings/about_strings.rb +56 -0
  368. data/solutions/about-symbols/about_symbols.rb +35 -0
  369. data/solutions/about-to-str/about_to_str.rb +24 -0
  370. data/solutions/about-triangle-project/about_triangle_project.rb +19 -0
  371. data/solutions/about-triangle-project-two/about_triangle_project_two.rb +25 -0
  372. data/solutions/about-true-and-false/about_true_and_false.rb +25 -0
  373. data/solutions/blocks-and-methods/blocks_and_methods.rb +14 -0
  374. data/solutions/class-state/class_state.rb +33 -0
  375. data/solutions/closure-capture/closure_capture.rb +12 -0
  376. data/solutions/collection-basics/collection_basics.rb +14 -0
  377. data/solutions/constant-scope/constant_scope.rb +18 -0
  378. data/solutions/core-protocols/core_protocols.rb +44 -0
  379. data/solutions/delegation/delegation.rb +17 -0
  380. data/solutions/dynamic-methods/dynamic_methods.rb +32 -0
  381. data/solutions/exception-flow/exception_flow.rb +34 -0
  382. data/solutions/heredoc-layout/heredoc_layout.rb +24 -0
  383. data/solutions/keyword-forwarding/keyword_forwarding.rb +26 -0
  384. data/solutions/lazy-enumeration/lazy_enumeration.rb +11 -0
  385. data/solutions/literals-and-operators/literals_and_operators.rb +22 -0
  386. data/solutions/method-contracts/method_contracts.rb +18 -0
  387. data/solutions/method-lookup/method_lookup.rb +26 -0
  388. data/solutions/nonlocal-exit/nonlocal_exit.rb +46 -0
  389. data/solutions/numbered-parameters/numbered_parameters.rb +18 -0
  390. data/solutions/numeric-precision/numeric_precision.rb +17 -0
  391. data/solutions/pattern-matching/pattern_matching.rb +17 -0
  392. data/solutions/refinement-scope/refinement_scope.rb +34 -0
  393. data/solutions/regexp-captures/regexp_captures.rb +24 -0
  394. data/solutions/runtime-context/runtime_context.rb +19 -0
  395. data/solutions/shared-rows/shared_rows.rb +13 -0
  396. data/solutions/singleton-pattern/singleton_pattern.rb +19 -0
  397. data/solutions/time-and-date/time_and_date.rb +16 -0
  398. data/solutions/visibility/visibility.rb +34 -0
  399. data/test/exercises/about_array_assignment_test.rb +87 -0
  400. data/test/exercises/about_arrays_test.rb +119 -0
  401. data/test/exercises/about_asserts_test.rb +41 -0
  402. data/test/exercises/about_blocks_test.rb +140 -0
  403. data/test/exercises/about_class_methods_test.rb +232 -0
  404. data/test/exercises/about_classes_test.rb +261 -0
  405. data/test/exercises/about_constants_test.rb +111 -0
  406. data/test/exercises/about_control_statements_test.rb +188 -0
  407. data/test/exercises/about_dice_project_test.rb +40 -0
  408. data/test/exercises/about_exceptions_test.rb +98 -0
  409. data/test/exercises/about_extra_credit_test.rb +74 -0
  410. data/test/exercises/about_hashes_test.rb +163 -0
  411. data/test/exercises/about_inheritance_test.rb +121 -0
  412. data/test/exercises/about_iteration_test.rb +148 -0
  413. data/test/exercises/about_keyword_arguments_test.rb +80 -0
  414. data/test/exercises/about_message_passing_test.rb +230 -0
  415. data/test/exercises/about_methods_test.rb +208 -0
  416. data/test/exercises/about_modules_test.rb +98 -0
  417. data/test/exercises/about_nil_test.rb +35 -0
  418. data/test/exercises/about_objects_test.rb +74 -0
  419. data/test/exercises/about_open_classes_test.rb +60 -0
  420. data/test/exercises/about_pattern_matching_test.rb +257 -0
  421. data/test/exercises/about_proxy_object_project_test.rb +75 -0
  422. data/test/exercises/about_regular_expressions_test.rb +243 -0
  423. data/test/exercises/about_sandwich_code_test.rb +82 -0
  424. data/test/exercises/about_scope_test.rb +123 -0
  425. data/test/exercises/about_scoring_project_test.rb +62 -0
  426. data/test/exercises/about_strings_test.rb +255 -0
  427. data/test/exercises/about_symbols_test.rb +120 -0
  428. data/test/exercises/about_to_str_test.rb +89 -0
  429. data/test/exercises/about_triangle_project_test.rb +29 -0
  430. data/test/exercises/about_triangle_project_two_test.rb +28 -0
  431. data/test/exercises/about_true_and_false_test.rb +43 -0
  432. data/test/exercises/blocks_and_methods_test.rb +22 -0
  433. data/test/exercises/class_state_test.rb +29 -0
  434. data/test/exercises/closure_capture_test.rb +29 -0
  435. data/test/exercises/collection_basics_test.rb +18 -0
  436. data/test/exercises/constant_scope_test.rb +18 -0
  437. data/test/exercises/core_protocols_test.rb +28 -0
  438. data/test/exercises/delegation_test.rb +23 -0
  439. data/test/exercises/dynamic_methods_test.rb +28 -0
  440. data/test/exercises/exception_flow_test.rb +17 -0
  441. data/test/exercises/heredoc_layout_test.rb +18 -0
  442. data/test/exercises/keyword_forwarding_test.rb +22 -0
  443. data/test/exercises/lazy_enumeration_test.rb +15 -0
  444. data/test/exercises/literals_and_operators_test.rb +25 -0
  445. data/test/exercises/method_contracts_test.rb +28 -0
  446. data/test/exercises/method_lookup_test.rb +14 -0
  447. data/test/exercises/nonlocal_exit_test.rb +22 -0
  448. data/test/exercises/numbered_parameters_test.rb +15 -0
  449. data/test/exercises/numeric_precision_test.rb +24 -0
  450. data/test/exercises/pattern_matching_test.rb +18 -0
  451. data/test/exercises/refinement_scope_test.rb +14 -0
  452. data/test/exercises/regexp_captures_test.rb +20 -0
  453. data/test/exercises/runtime_context_test.rb +59 -0
  454. data/test/exercises/shared_rows_test.rb +34 -0
  455. data/test/exercises/singleton_pattern_test.rb +16 -0
  456. data/test/exercises/time_and_date_test.rb +22 -0
  457. data/test/exercises/visibility_test.rb +22 -0
  458. metadata +471 -4
  459. data/Rakefile +0 -12
  460. data/sig/test_rkoans.rbs +0 -3
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exercises
4
+ # Reads the process inputs and identifies the running Ruby files and version.
5
+ class RuntimeContext
6
+ # Return the context hash described in the brief without consuming arguments.
7
+ def self.context
8
+ { arguments: [], greeting: '', program: '', file: '', encoding: '', ruby: '' }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # Three Rows, One Surprise
2
+
3
+ Fix `Exercises::SharedRows.rows(count)` in `shared_rows.rb` so it returns the
4
+ requested number of empty, mutable arrays. Changing one row must leave the
5
+ others untouched. Return `[]` for zero rows and fresh rows on each call.
6
+
7
+ The starter supplies one array as the initial value to `Array.new`. Explore why
8
+ appending to one row changes the others, then change how the rows are created.
@@ -0,0 +1,7 @@
1
+ # Hint
2
+
3
+ An array holds references to objects. Passing a second argument to `Array.new`
4
+ places that same object in every position; it does not copy it.
5
+
6
+ The block form runs once per position. What could the block return so each row
7
+ has its own mutable contents?
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exercises
4
+ # Builds rows whose mutable contents are independent.
5
+ class SharedRows
6
+ # Returns count empty rows, each with its own mutable array.
7
+ # @param count [Integer] nonnegative number of rows
8
+ # @return [Array<Array<Object>>]
9
+ def self.rows(count)
10
+ Array.new(count, [])
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ # One configuration object
2
+
3
+ Use the standard-library Singleton module so Configuration.instance returns the same instance and Configuration.new is not a public constructor. Keep the default mode "learning". Distinguish this module from singleton methods, which are methods on one object.
4
+
5
+ Use `rkoans check` to investigate one failing check at a time. Hints are optional.
6
+
7
+ Edit `singleton_pattern.rb` in this exercise's folder. Checks call
8
+ `Exercises::SingletonPattern` and its example classes.
@@ -0,0 +1,5 @@
1
+ # Hint
2
+
3
+ Reusing the same instance is different from creating a fresh one on every call.
4
+
5
+ The Singleton module supplies instance and changes construction visibility.
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ module Exercises
5
+ # Use the standard-library Singleton module so Configuration.instance returns the same instance
6
+ # and Configuration.new is not a public constructor.
7
+ class SingletonPattern
8
+ # Example Configuration for singleton pattern.
9
+ class Configuration
10
+ attr_accessor :mode
11
+
12
+ # Stores the initial values used by this example.
13
+ # @return [void]
14
+ def initialize = @mode = 'learning'
15
+ # Returns the shared configuration instance.
16
+ # @return [Configuration]
17
+ def self.instance = new
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # Offsets and leap days
2
+
3
+ Implement `utc(text)` to parse an ISO8601 timestamp with an explicit offset and return the same instant formatted in UTC. Implement `days_between(first,last)` using Date ISO8601 parsing; return the integer number of calendar days, including leap days. Do not read the current clock.
4
+
5
+ Use `rkoans check` to investigate one failing check at a time. Hints are optional.
6
+
7
+ Edit `time_and_date.rb` in this exercise's folder. Checks call
8
+ `Exercises::TimeAndDate` and its example classes.
@@ -0,0 +1,5 @@
1
+ # Hint
2
+
3
+ An offset is part of the timestamp; UTC conversion must preserve the instant.
4
+
5
+ Date subtraction returns an exact difference in calendar days.
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The example deliberately contrasts received arguments with values actually used.
4
+ # rubocop:disable Lint/UnusedMethodArgument
5
+
6
+ require 'time'
7
+ require 'date'
8
+ module Exercises
9
+ # Implement utc(text) to parse an ISO8601 timestamp with an explicit offset and return the same
10
+ # instant formatted in UTC.
11
+ class TimeAndDate
12
+ # Normalizes an offset ISO8601 timestamp to its UTC representation.
13
+ # @return [String]
14
+ def self.utc(text) = Time.iso8601(text).iso8601
15
+ # Returns the signed integer calendar-day difference between ISO dates.
16
+ # @return [Integer]
17
+ def self.days_between(first, last) = 0
18
+ end
19
+ end
20
+
21
+ # rubocop:enable Lint/UnusedMethodArgument
@@ -0,0 +1,8 @@
1
+ # An API boundary
2
+
3
+ Wallet has an internal balance reader used when comparing wallets, and a normalizer used within deposit. Make balance protected, normalize private, and deposit/compare public. Other wallet instances must be able to compare balances, while callers cannot invoke balance or normalize directly. Preserve explicit self.normalize inside deposit: it is valid on the current Ruby.
4
+
5
+ Use `rkoans check` to investigate one failing check at a time. Hints are optional.
6
+
7
+ Edit `visibility.rb` in this exercise's folder. Checks call
8
+ `Exercises::Visibility` and its example classes.
@@ -0,0 +1,5 @@
1
+ # Hint
2
+
3
+ Protected methods allow suitable peer-object calls; private methods belong to internal use.
4
+
5
+ Visibility declarations affect instance methods, not automatically singleton methods.
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The explicit receiver is part of the method-call experiment.
4
+ # Explicit getter definitions are compared with attr_reader and attr_accessor.
5
+ # rubocop:disable Style/RedundantSelf, Style/TrivialAccessors
6
+
7
+ module Exercises
8
+ # Wallet has an internal balance reader used when comparing wallets, and a normalizer used within
9
+ # deposit.
10
+ class Visibility
11
+ # Example Wallet for visibility.
12
+ class Wallet
13
+ # Stores the initial values used by this example.
14
+ # @return [void]
15
+ def initialize(balance) = @balance = balance
16
+ # Returns the protected balance used for peer comparisons.
17
+ # @return [Integer]
18
+ def balance = @balance
19
+ # Converts a deposit amount to its nonnegative value.
20
+ # @return [Integer]
21
+ def normalize(value) = value.abs
22
+ # Adds the normalized amount to the balance.
23
+ # @return [Integer]
24
+ def deposit(value) = @balance += self.normalize(value)
25
+ # Compares this balance with another wallet's balance.
26
+ # @return [Integer]
27
+ def compare(other) = balance <=> other.balance
28
+ end
29
+ end
30
+ end
31
+
32
+ # rubocop:enable Style/RedundantSelf, Style/TrivialAccessors
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'error'
5
+
6
+ module Rkoans
7
+ # Loads the ordered exercise IDs and their packaged asset root.
8
+ class Catalog
9
+ attr_reader :root, :ids
10
+
11
+ # @param root [String] directory containing exercises, checks, and references
12
+ def initialize(root: File.expand_path('../..', __dir__))
13
+ @root = File.expand_path(root)
14
+ @ids = JSON.parse(File.read(File.join(@root, 'exercises/catalog.json')))
15
+ raise Error, 'Invalid exercise catalog: expected unique exercise IDs in order.' unless valid_ids?
16
+
17
+ @ids.each(&:freeze)
18
+ @ids.freeze
19
+ rescue JSON::ParserError, SystemCallError => e
20
+ raise Error, "Invalid exercise catalog: #{e.message}"
21
+ end
22
+
23
+ private
24
+
25
+ # @return [Boolean] whether IDs are unique, nonempty, and safe for asset paths
26
+ def valid_ids?
27
+ @ids.is_a?(Array) && !@ids.empty? && @ids.uniq == @ids &&
28
+ @ids.all? { |id| id.is_a?(String) && id.match?(/\A[a-z]+(?:-[a-z]+)*\z/) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rbconfig'
4
+ require 'tempfile'
5
+ require 'timeout'
6
+
7
+ module Rkoans
8
+ # Runs ordinary Minitest checks in a child process with a time limit.
9
+ class Checker
10
+ MAX_KB = 65_536
11
+ Result = Data.define(:output, :exit_code)
12
+
13
+ # @param [Integer | Float] timeout
14
+ def initialize(timeout: 30)
15
+ @timeout = timeout
16
+ end
17
+
18
+ # @param [Rkoans::Exercise] exercise
19
+ # @return [Rkoans::Checker::Result]
20
+ def run(exercise)
21
+ Tempfile.create('rkoans-check') do |output|
22
+ pid = Process.spawn(
23
+ RbConfig.ruby, '-rminitest/autorun', exercise.checks, exercise.solution,
24
+ in: File::NULL, out: output, err: output, pgroup: true
25
+ )
26
+ begin
27
+ status = Timeout.timeout(@timeout) { Process.wait2(pid).last }
28
+ output.rewind
29
+ Result.new(output.read(MAX_KB), status.exitstatus || 1)
30
+ rescue Timeout::Error
31
+ Result.new("Checks timed out after #{@timeout} seconds.\n", 1)
32
+ ensure
33
+ stop_group(pid:)
34
+ begin
35
+ Process.wait(pid)
36
+ rescue Errno::ECHILD
37
+ # The child has already been reaped.
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # @param [Integer] pid
46
+ # @return [Integer, nil]
47
+ def stop_group(pid:)
48
+ Process.kill('TERM', -pid)
49
+ sleep 0.05
50
+ reap_exited_child(pid)
51
+ Process.kill('KILL', -pid)
52
+ rescue Errno::ESRCH
53
+ nil
54
+ end
55
+
56
+ def reap_exited_child(pid)
57
+ Process.waitpid(pid, Process::WNOHANG)
58
+ rescue Errno::ECHILD
59
+ nil
60
+ end
61
+ end
62
+ end
data/lib/rkoans/cli.rb ADDED
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rkoans
4
+ # Dispatches the command and renders each check run in the terminal.
5
+ class CLI
6
+ # @param [IO] out
7
+ # @param [IO] err
8
+ # @param [String] directory
9
+ # @param catalog [Catalog, nil] ordered exercises used by this component
10
+ def initialize(out: $stdout, err: $stderr, input: $stdin, directory: File.join(Dir.home, 'ruby-koans'),
11
+ catalog: nil)
12
+ @catalog = catalog
13
+ @out = out
14
+ @err = err
15
+ @directory = directory
16
+ @input = input
17
+ @session = nil
18
+ end
19
+
20
+ # @param [Array<String>] arguments
21
+ # @return [Integer]
22
+ def run(arguments)
23
+ @out.sync = true
24
+ command = arguments.empty? ? 'start' : arguments.first
25
+ return usage_error if arguments.size > 2 || (arguments.size == 2 && command != 'start')
26
+
27
+ dispatch(command, arguments[1])
28
+ rescue Interrupt
29
+ @out.puts "\nStopped. Your edits are preserved."
30
+ 130
31
+ rescue Error, SystemCallError => e
32
+ @err.puts e.message
33
+ 2
34
+ end
35
+
36
+ private
37
+
38
+ # Loads installation data inside run's setup-error handler, only when needed.
39
+ # @return [Catalog]
40
+ def catalog
41
+ @catalog ||= Catalog.new
42
+ end
43
+
44
+ # Creates the interactive coordinator after the catalog has been validated.
45
+ # @return [Session]
46
+ def session
47
+ @session ||= Session.new(directory: @directory, out: @out, input: @input, catalog: catalog)
48
+ end
49
+
50
+ def dispatch(command, id)
51
+ case command
52
+ when 'start'
53
+ session.start(id)
54
+ when 'check'
55
+ session.check
56
+ when 'list'
57
+ @out.puts catalog.ids
58
+ 0
59
+ when '--help', '-h'
60
+ help
61
+ else
62
+ usage_error
63
+ end
64
+ end
65
+
66
+ # @return [Integer]
67
+ def help
68
+ @out.puts 'Usage: rkoans [start [EXERCISE]|check|list]'
69
+ @out.puts 'Start watches your exercise for saves. Ctrl-C exits. No command means start.'
70
+ 0
71
+ end
72
+
73
+ # @return [Integer]
74
+ def usage_error
75
+ @err.puts 'Usage: rkoans [start [EXERCISE]|check|list]'
76
+ 2
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rkoans
4
+ # Identifies command and installation errors that can be reported without a backtrace.
5
+ class Error < StandardError; end
6
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Rkoans
6
+ # Locates the packaged exercise and creates its editable learner copy.
7
+ class Exercise
8
+ attr_reader :solution, :id
9
+
10
+ # @param [String] directory
11
+ # @param catalog [Catalog] source assets and allowed exercise IDs
12
+ def initialize(directory: File.join(Dir.home, 'ruby-koans'), catalog: Catalog.new, id: catalog.ids.first)
13
+ raise Error, "Unknown exercise: #{id}; run rkoans list" unless catalog.ids.include?(id)
14
+
15
+ @id = id
16
+ @root = catalog.root
17
+ @source = File.join(@root, "exercises/#{id}")
18
+ @solution = File.join(File.expand_path(directory), 'exercises', id, "#{id.tr('-', '_')}.rb")
19
+ end
20
+
21
+ # @return [Integer, nil]
22
+ def prepare
23
+ FileUtils.mkdir_p(File.dirname(solution))
24
+ File.open(solution, File::WRONLY | File::CREAT | File::EXCL) do |file|
25
+ file.write(File.read(File.join(@source, File.basename(solution))))
26
+ end
27
+ rescue Errno::EEXIST
28
+ # Starting again preserves the learner's work.
29
+ nil
30
+ end
31
+
32
+ # @return [String]
33
+ def instructions
34
+ File.read(File.join(@source, 'brief.md'))
35
+ end
36
+
37
+ # @return [String]
38
+ def checks
39
+ File.join(@root, "test/exercises/#{id.tr('-', '_')}_test.rb")
40
+ end
41
+
42
+ def reference
43
+ File.join(@root, "solutions/#{id}/#{File.basename(solution)}")
44
+ end
45
+
46
+ def hint
47
+ File.read(File.join(@source, 'hint.md'))
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+ require 'io/wait'
5
+
6
+ module Rkoans
7
+ # Reads immediate keys while reserving Enter confirmation for next and quit.
8
+ class Keyboard
9
+ def initialize(input:)
10
+ @input = input
11
+ @pending = nil
12
+ end
13
+
14
+ def with_terminal(&block)
15
+ if @input&.tty?
16
+ @input.raw(intr: true, &block)
17
+ else
18
+ block.call
19
+ end
20
+ end
21
+
22
+ def read(interval)
23
+ unless @input
24
+ sleep interval
25
+ return
26
+ end
27
+ return unless @input.wait_readable(interval)
28
+
29
+ key = @input.read_nonblock(1, exception: false)
30
+ @input = nil unless key
31
+ decode(key) if key.is_a?(String)
32
+ end
33
+
34
+ private
35
+
36
+ def decode(key)
37
+ case key
38
+ when "\n", "\r"
39
+ command = @pending
40
+ @pending = nil
41
+ command
42
+ when 'n', 'q'
43
+ @pending = key
44
+ nil
45
+ else
46
+ @pending = nil
47
+ key unless ["\e", "\b", "\u007f", ' '].include?(key)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+ require 'tempfile'
6
+
7
+ module Rkoans
8
+ # Stores completed exercise IDs independently of the current selection.
9
+ class Progress
10
+ # @param catalog [Catalog] ordered exercises used by this component
11
+ def initialize(directory:, catalog: Catalog.new)
12
+ @catalog = catalog
13
+ @path = File.join(directory, '.rkoans', 'completed.json')
14
+ end
15
+
16
+ def completed
17
+ ids = JSON.parse(File.read(@path))
18
+ raise Error, "Invalid progress file: #{@path}" unless ids.is_a?(Array) && ids.all?(String)
19
+
20
+ ids & @catalog.ids
21
+ rescue Errno::ENOENT
22
+ []
23
+ rescue JSON::ParserError
24
+ raise Error, "Invalid progress file: #{@path}"
25
+ end
26
+
27
+ def mark_done(id)
28
+ ids = completed
29
+ return if ids.include?(id)
30
+
31
+ FileUtils.mkdir_p(File.dirname(@path))
32
+ Tempfile.create('completed', File.dirname(@path)) do |file|
33
+ file.write(JSON.generate(ids + [id]))
34
+ file.close
35
+ File.rename(file.path, @path)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'tempfile'
5
+
6
+ module Rkoans
7
+ # Remembers the active exercise without changing any learner files.
8
+ class Selection
9
+ # @param catalog [Catalog] ordered exercises used by this component
10
+ def initialize(directory:, catalog: Catalog.new)
11
+ @catalog = catalog
12
+ @path = File.join(directory, '.rkoans', 'selection')
13
+ end
14
+
15
+ def read
16
+ File.read(@path).strip
17
+ rescue Errno::ENOENT
18
+ @catalog.ids.first
19
+ end
20
+
21
+ def write(id)
22
+ directory = File.dirname(@path)
23
+ FileUtils.mkdir_p(directory)
24
+ Tempfile.create('selection', directory) do |file|
25
+ file.write("#{id}\n")
26
+ file.close
27
+ File.rename(file.path, @path)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rkoans
4
+ # Coordinates checks, file watching, keyboard commands, and completion.
5
+ class Session
6
+ # @param catalog [Catalog] ordered exercises used by this component
7
+ def initialize(directory:, out:, input:, catalog: Catalog.new)
8
+ @catalog = catalog
9
+ @directory = directory
10
+ @input = input
11
+ @listing = false
12
+ @selection = Selection.new(directory: directory, catalog: catalog)
13
+ @checker = Checker.new
14
+ @progress = Progress.new(directory: directory, catalog: catalog)
15
+ @view = TerminalView.new(out: out, catalog: catalog)
16
+ end
17
+
18
+ # Keeps the current exercise open until the user advances or quits.
19
+ def start(id)
20
+ selected = id || @selection.read
21
+ loop do
22
+ activate(selected)
23
+ action = watch
24
+ return 0 if action == :quit
25
+
26
+ selected = action
27
+ end
28
+ end
29
+
30
+ # @return [Integer]
31
+ def check
32
+ @exercise = Exercise.new(directory: @directory, id: @selection.read, catalog: @catalog)
33
+ raise Error, 'No exercise found. Run rkoans start first.' unless File.file?(@exercise.solution)
34
+
35
+ show_checks(watching: false)
36
+ end
37
+
38
+ private
39
+
40
+ def activate(id)
41
+ @listing = false
42
+ @exercise = Exercise.new(directory: @directory, id: id, catalog: @catalog)
43
+ @exercise.prepare
44
+ @selection.write(id)
45
+ end
46
+
47
+ def watch
48
+ catch(:session_action) do
49
+ Watcher.new(@exercise.solution).run(input: @input) do |command|
50
+ command ? handle_command(command) : show_checks(watching: true)
51
+ end
52
+ end
53
+ end
54
+
55
+ def handle_command(command)
56
+ case command
57
+ when 'n'
58
+ @listing = false
59
+ advance
60
+ when 'q'
61
+ show_message('Stopped. Your edits are preserved.', watching: false)
62
+ throw :session_action, :quit
63
+ when 'l', 's'
64
+ @listing = command == 'l'
65
+ show_checks(watching: true)
66
+ when 'h' then show_hint
67
+ when '?' then show_message(TerminalView::HELP)
68
+ else
69
+ show_message('Press ? for keyboard help.')
70
+ end
71
+ end
72
+
73
+ def advance
74
+ result = check_result
75
+ unless result.exit_code.zero?
76
+ render(result, watching: true, message: 'Finish the current exercise before moving on.')
77
+ return
78
+ end
79
+ ids = @catalog.ids
80
+ next_id = ids[ids.index(@exercise.id) + 1]
81
+ throw :session_action, next_id if next_id
82
+
83
+ message = if @progress.completed.size == ids.size
84
+ 'All exercises complete. You can keep experimenting or enter q to quit.'
85
+ else
86
+ 'No next exercise. Other exercises remain; enter l to list them or q to quit.'
87
+ end
88
+ render(result, watching: true, message: message)
89
+ end
90
+
91
+ # @param [Boolean] watching
92
+ # @return [Integer]
93
+ def show_checks(watching:)
94
+ render(check_result, watching: watching)
95
+ end
96
+
97
+ def check_result
98
+ result = @checker.run(@exercise)
99
+ @passed = result.exit_code.zero?
100
+ @progress.mark_done(@exercise.id) if @passed
101
+ result
102
+ end
103
+
104
+ def render(result, watching:, message: nil)
105
+ if watching && @listing
106
+ @view.show_list(exercise: @exercise, completed: @progress.completed, passed: @passed)
107
+ else
108
+ @view.render(exercise: @exercise, result: result, completed: @progress.completed,
109
+ watching: watching, message: message)
110
+ end
111
+ result.exit_code
112
+ end
113
+
114
+ def show_message(message, watching: true)
115
+ mode = @listing ? :list : :current
116
+ mode = :quit unless watching
117
+ @view.show_message(message, exercise: @exercise, completed: @progress.completed,
118
+ passed: @passed, mode: mode)
119
+ end
120
+
121
+ def show_hint
122
+ return if @listing || @passed
123
+
124
+ show_message(@exercise.hint)
125
+ end
126
+ end
127
+ end