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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/CURRICULUM.md +86 -0
- data/README.md +77 -0
- data/THIRD_PARTY_NOTICES.md +30 -0
- data/exercises/about-array-assignment/about_array_assignment.rb +45 -0
- data/exercises/about-array-assignment/brief.md +11 -0
- data/exercises/about-array-assignment/hint.md +11 -0
- data/exercises/about-arrays/about_arrays.rb +77 -0
- data/exercises/about-arrays/brief.md +16 -0
- data/exercises/about-arrays/hint.md +13 -0
- data/exercises/about-asserts/about_asserts.rb +22 -0
- data/exercises/about-asserts/brief.md +22 -0
- data/exercises/about-asserts/hint.md +10 -0
- data/exercises/about-blocks/about_blocks.rb +76 -0
- data/exercises/about-blocks/brief.md +44 -0
- data/exercises/about-blocks/hint.md +7 -0
- data/exercises/about-class-methods/about_class_methods.rb +105 -0
- data/exercises/about-class-methods/brief.md +73 -0
- data/exercises/about-class-methods/hint.md +8 -0
- data/exercises/about-classes/about_classes.rb +131 -0
- data/exercises/about-classes/brief.md +73 -0
- data/exercises/about-classes/hint.md +7 -0
- data/exercises/about-constants/about_constants.rb +49 -0
- data/exercises/about-constants/brief.md +51 -0
- data/exercises/about-constants/hint.md +7 -0
- data/exercises/about-control-statements/about_control_statements.rb +152 -0
- data/exercises/about-control-statements/brief.md +9 -0
- data/exercises/about-control-statements/hint.md +7 -0
- data/exercises/about-dice-project/about_dice_project.rb +27 -0
- data/exercises/about-dice-project/brief.md +12 -0
- data/exercises/about-dice-project/hint.md +5 -0
- data/exercises/about-exceptions/about_exceptions.rb +84 -0
- data/exercises/about-exceptions/brief.md +19 -0
- data/exercises/about-exceptions/hint.md +8 -0
- data/exercises/about-extra-credit/about_extra_credit.rb +117 -0
- data/exercises/about-extra-credit/brief.md +28 -0
- data/exercises/about-extra-credit/hint.md +5 -0
- data/exercises/about-hashes/about_hashes.rb +160 -0
- data/exercises/about-hashes/brief.md +9 -0
- data/exercises/about-hashes/hint.md +8 -0
- data/exercises/about-inheritance/about_inheritance.rb +63 -0
- data/exercises/about-inheritance/brief.md +50 -0
- data/exercises/about-inheritance/hint.md +8 -0
- data/exercises/about-iteration/about_iteration.rb +96 -0
- data/exercises/about-iteration/brief.md +26 -0
- data/exercises/about-iteration/hint.md +6 -0
- data/exercises/about-keyword-arguments/about_keyword_arguments.rb +56 -0
- data/exercises/about-keyword-arguments/brief.md +28 -0
- data/exercises/about-keyword-arguments/hint.md +7 -0
- data/exercises/about-message-passing/about_message_passing.rb +112 -0
- data/exercises/about-message-passing/brief.md +59 -0
- data/exercises/about-message-passing/hint.md +8 -0
- data/exercises/about-methods/about_methods.rb +104 -0
- data/exercises/about-methods/brief.md +55 -0
- data/exercises/about-methods/hint.md +8 -0
- data/exercises/about-modules/about_modules.rb +43 -0
- data/exercises/about-modules/brief.md +44 -0
- data/exercises/about-modules/hint.md +8 -0
- data/exercises/about-nil/about_nil.rb +29 -0
- data/exercises/about-nil/brief.md +12 -0
- data/exercises/about-nil/hint.md +11 -0
- data/exercises/about-objects/about_objects.rb +51 -0
- data/exercises/about-objects/brief.md +14 -0
- data/exercises/about-objects/hint.md +12 -0
- data/exercises/about-open-classes/about_open_classes.rb +38 -0
- data/exercises/about-open-classes/brief.md +34 -0
- data/exercises/about-open-classes/hint.md +7 -0
- data/exercises/about-pattern-matching/about_pattern_matching.rb +106 -0
- data/exercises/about-pattern-matching/brief.md +124 -0
- data/exercises/about-pattern-matching/hint.md +8 -0
- data/exercises/about-proxy-object-project/about_proxy_object_project.rb +43 -0
- data/exercises/about-proxy-object-project/brief.md +10 -0
- data/exercises/about-proxy-object-project/hint.md +5 -0
- data/exercises/about-regular-expressions/about_regular_expressions.rb +174 -0
- data/exercises/about-regular-expressions/brief.md +9 -0
- data/exercises/about-regular-expressions/hint.md +8 -0
- data/exercises/about-sandwich-code/about_sandwich_code.rb +58 -0
- data/exercises/about-sandwich-code/brief.md +14 -0
- data/exercises/about-sandwich-code/hint.md +5 -0
- data/exercises/about-scope/about_scope.rb +76 -0
- data/exercises/about-scope/brief.md +35 -0
- data/exercises/about-scope/hint.md +8 -0
- data/exercises/about-scoring-project/about_scoring_project.rb +16 -0
- data/exercises/about-scoring-project/brief.md +10 -0
- data/exercises/about-scoring-project/hint.md +5 -0
- data/exercises/about-strings/about_strings.rb +142 -0
- data/exercises/about-strings/brief.md +22 -0
- data/exercises/about-strings/hint.md +11 -0
- data/exercises/about-symbols/about_symbols.rb +53 -0
- data/exercises/about-symbols/brief.md +21 -0
- data/exercises/about-symbols/hint.md +12 -0
- data/exercises/about-to-str/about_to_str.rb +49 -0
- data/exercises/about-to-str/brief.md +37 -0
- data/exercises/about-to-str/hint.md +7 -0
- data/exercises/about-triangle-project/about_triangle_project.rb +15 -0
- data/exercises/about-triangle-project/brief.md +11 -0
- data/exercises/about-triangle-project/hint.md +5 -0
- data/exercises/about-triangle-project-two/about_triangle_project_two.rb +22 -0
- data/exercises/about-triangle-project-two/brief.md +10 -0
- data/exercises/about-triangle-project-two/hint.md +5 -0
- data/exercises/about-true-and-false/about_true_and_false.rb +26 -0
- data/exercises/about-true-and-false/brief.md +22 -0
- data/exercises/about-true-and-false/hint.md +7 -0
- data/exercises/blocks-and-methods/blocks_and_methods.rb +14 -0
- data/exercises/blocks-and-methods/brief.md +8 -0
- data/exercises/blocks-and-methods/hint.md +6 -0
- data/exercises/catalog.json +61 -0
- data/exercises/class-state/brief.md +8 -0
- data/exercises/class-state/class_state.rb +39 -0
- data/exercises/class-state/hint.md +5 -0
- data/exercises/closure-capture/brief.md +8 -0
- data/exercises/closure-capture/closure_capture.rb +21 -0
- data/exercises/closure-capture/hint.md +5 -0
- data/exercises/collection-basics/brief.md +8 -0
- data/exercises/collection-basics/collection_basics.rb +16 -0
- data/exercises/collection-basics/hint.md +6 -0
- data/exercises/constant-scope/brief.md +8 -0
- data/exercises/constant-scope/constant_scope.rb +18 -0
- data/exercises/constant-scope/hint.md +5 -0
- data/exercises/core-protocols/brief.md +8 -0
- data/exercises/core-protocols/core_protocols.rb +43 -0
- data/exercises/core-protocols/hint.md +5 -0
- data/exercises/delegation/brief.md +8 -0
- data/exercises/delegation/delegation.rb +20 -0
- data/exercises/delegation/hint.md +5 -0
- data/exercises/dynamic-methods/brief.md +8 -0
- data/exercises/dynamic-methods/dynamic_methods.rb +33 -0
- data/exercises/dynamic-methods/hint.md +5 -0
- data/exercises/exception-flow/brief.md +8 -0
- data/exercises/exception-flow/exception_flow.rb +27 -0
- data/exercises/exception-flow/hint.md +5 -0
- data/exercises/heredoc-layout/brief.md +14 -0
- data/exercises/heredoc-layout/heredoc_layout.rb +25 -0
- data/exercises/heredoc-layout/hint.md +9 -0
- data/exercises/keyword-forwarding/brief.md +8 -0
- data/exercises/keyword-forwarding/hint.md +5 -0
- data/exercises/keyword-forwarding/keyword_forwarding.rb +27 -0
- data/exercises/lazy-enumeration/brief.md +8 -0
- data/exercises/lazy-enumeration/hint.md +5 -0
- data/exercises/lazy-enumeration/lazy_enumeration.rb +16 -0
- data/exercises/literals-and-operators/brief.md +20 -0
- data/exercises/literals-and-operators/hint.md +5 -0
- data/exercises/literals-and-operators/literals_and_operators.rb +15 -0
- data/exercises/method-contracts/brief.md +8 -0
- data/exercises/method-contracts/hint.md +5 -0
- data/exercises/method-contracts/method_contracts.rb +22 -0
- data/exercises/method-lookup/brief.md +8 -0
- data/exercises/method-lookup/hint.md +5 -0
- data/exercises/method-lookup/method_lookup.rb +26 -0
- data/exercises/nonlocal-exit/brief.md +8 -0
- data/exercises/nonlocal-exit/hint.md +5 -0
- data/exercises/nonlocal-exit/nonlocal_exit.rb +29 -0
- data/exercises/numbered-parameters/brief.md +18 -0
- data/exercises/numbered-parameters/hint.md +5 -0
- data/exercises/numbered-parameters/numbered_parameters.rb +18 -0
- data/exercises/numeric-precision/brief.md +8 -0
- data/exercises/numeric-precision/hint.md +5 -0
- data/exercises/numeric-precision/numeric_precision.rb +17 -0
- data/exercises/pattern-matching/brief.md +8 -0
- data/exercises/pattern-matching/hint.md +5 -0
- data/exercises/pattern-matching/pattern_matching.rb +22 -0
- data/exercises/refinement-scope/brief.md +8 -0
- data/exercises/refinement-scope/hint.md +5 -0
- data/exercises/refinement-scope/refinement_scope.rb +33 -0
- data/exercises/regexp-captures/brief.md +8 -0
- data/exercises/regexp-captures/hint.md +5 -0
- data/exercises/regexp-captures/regexp_captures.rb +25 -0
- data/exercises/runtime-context/brief.md +20 -0
- data/exercises/runtime-context/hint.md +8 -0
- data/exercises/runtime-context/runtime_context.rb +11 -0
- data/exercises/shared-rows/brief.md +8 -0
- data/exercises/shared-rows/hint.md +7 -0
- data/exercises/shared-rows/shared_rows.rb +13 -0
- data/exercises/singleton-pattern/brief.md +8 -0
- data/exercises/singleton-pattern/hint.md +5 -0
- data/exercises/singleton-pattern/singleton_pattern.rb +20 -0
- data/exercises/time-and-date/brief.md +8 -0
- data/exercises/time-and-date/hint.md +5 -0
- data/exercises/time-and-date/time_and_date.rb +21 -0
- data/exercises/visibility/brief.md +8 -0
- data/exercises/visibility/hint.md +5 -0
- data/exercises/visibility/visibility.rb +32 -0
- data/lib/rkoans/catalog.rb +31 -0
- data/lib/rkoans/checker.rb +62 -0
- data/lib/rkoans/cli.rb +79 -0
- data/lib/rkoans/error.rb +6 -0
- data/lib/rkoans/exercise.rb +50 -0
- data/lib/rkoans/keyboard.rb +51 -0
- data/lib/rkoans/progress.rb +39 -0
- data/lib/rkoans/selection.rb +31 -0
- data/lib/rkoans/session.rb +127 -0
- data/lib/rkoans/terminal_view.rb +99 -0
- data/lib/rkoans/version.rb +1 -1
- data/lib/rkoans/watcher.rb +58 -0
- data/lib/rkoans.rb +13 -3
- data/sig/about_array_assignment_acceptance_test.rbs +3 -0
- data/sig/about_arrays_acceptance_test.rbs +3 -0
- data/sig/about_asserts_command_test.rbs +4 -0
- data/sig/about_blocks_acceptance_test.rbs +3 -0
- data/sig/about_class_methods_acceptance_test.rbs +3 -0
- data/sig/about_classes_acceptance_test.rbs +3 -0
- data/sig/about_constants_acceptance_test.rbs +3 -0
- data/sig/about_control_statements_acceptance_test.rbs +3 -0
- data/sig/about_dice_project_acceptance_test.rbs +3 -0
- data/sig/about_exceptions_acceptance_test.rbs +3 -0
- data/sig/about_extra_credit_acceptance_test.rbs +5 -0
- data/sig/about_hashes_acceptance_test.rbs +3 -0
- data/sig/about_inheritance_acceptance_test.rbs +3 -0
- data/sig/about_iteration_acceptance_test.rbs +3 -0
- data/sig/about_keyword_arguments_acceptance_test.rbs +3 -0
- data/sig/about_message_passing_acceptance_test.rbs +3 -0
- data/sig/about_methods_acceptance_test.rbs +3 -0
- data/sig/about_modules_acceptance_test.rbs +3 -0
- data/sig/about_nil_acceptance_test.rbs +3 -0
- data/sig/about_objects_acceptance_test.rbs +3 -0
- data/sig/about_open_classes_acceptance_test.rbs +3 -0
- data/sig/about_pattern_matching_acceptance_test.rbs +3 -0
- data/sig/about_proxy_object_project_acceptance_test.rbs +3 -0
- data/sig/about_regular_expressions_acceptance_test.rbs +3 -0
- data/sig/about_sandwich_code_acceptance_test.rbs +3 -0
- data/sig/about_scope_acceptance_test.rbs +3 -0
- data/sig/about_scoring_project_acceptance_test.rbs +3 -0
- data/sig/about_strings_acceptance_test.rbs +3 -0
- data/sig/about_symbols_acceptance_test.rbs +3 -0
- data/sig/about_to_str_acceptance_test.rbs +3 -0
- data/sig/about_triangle_project_acceptance_test.rbs +3 -0
- data/sig/about_triangle_project_two_acceptance_test.rbs +3 -0
- data/sig/about_true_and_false_command_test.rbs +4 -0
- data/sig/catalog_error_command_test.rbs +4 -0
- data/sig/checker_test.rbs +9 -0
- data/sig/class_state_acceptance_test.rbs +3 -0
- data/sig/closure_capture_acceptance_test.rbs +3 -0
- data/sig/command_support.rbs +18 -0
- data/sig/command_test.rbs +5 -0
- data/sig/constant_scope_acceptance_test.rbs +3 -0
- data/sig/core_protocols_acceptance_test.rbs +3 -0
- data/sig/curriculum_cop_test.rbs +9 -0
- data/sig/delegation_acceptance_test.rbs +3 -0
- data/sig/dynamic_methods_acceptance_test.rbs +3 -0
- data/sig/exception_flow_acceptance_test.rbs +3 -0
- data/sig/exercise_test.rbs +3 -0
- data/sig/exercises/about_array_assignment.rbs +36 -0
- data/sig/exercises/about_arrays.rbs +51 -0
- data/sig/exercises/about_asserts.rbs +23 -0
- data/sig/exercises/about_blocks.rbs +40 -0
- data/sig/exercises/about_class_methods.rbs +62 -0
- data/sig/exercises/about_classes.rbs +88 -0
- data/sig/exercises/about_constants.rbs +53 -0
- data/sig/exercises/about_control_statements.rbs +46 -0
- data/sig/exercises/about_dice_project.rbs +16 -0
- data/sig/exercises/about_exceptions.rbs +36 -0
- data/sig/exercises/about_extra_credit.rbs +36 -0
- data/sig/exercises/about_hashes.rbs +62 -0
- data/sig/exercises/about_inheritance.rbs +50 -0
- data/sig/exercises/about_iteration.rbs +37 -0
- data/sig/exercises/about_keyword_arguments.rbs +33 -0
- data/sig/exercises/about_message_passing.rbs +68 -0
- data/sig/exercises/about_methods.rbs +68 -0
- data/sig/exercises/about_modules.rbs +38 -0
- data/sig/exercises/about_nil.rbs +23 -0
- data/sig/exercises/about_objects.rbs +38 -0
- data/sig/exercises/about_open_classes.rbs +31 -0
- data/sig/exercises/about_pattern_matching.rbs +65 -0
- data/sig/exercises/about_proxy_object_project.rbs +31 -0
- data/sig/exercises/about_regular_expressions.rbs +83 -0
- data/sig/exercises/about_sandwich_code.rbs +17 -0
- data/sig/exercises/about_scope.rbs +59 -0
- data/sig/exercises/about_scoring_project.rbs +16 -0
- data/sig/exercises/about_strings.rbs +85 -0
- data/sig/exercises/about_symbols.rbs +46 -0
- data/sig/exercises/about_to_str.rbs +36 -0
- data/sig/exercises/about_triangle_project.rbs +11 -0
- data/sig/exercises/about_triangle_project_two.rbs +12 -0
- data/sig/exercises/about_true_and_false.rbs +27 -0
- data/sig/exercises/blocks_and_methods.rbs +11 -0
- data/sig/exercises/class_state.rbs +19 -0
- data/sig/exercises/closure_capture.rbs +10 -0
- data/sig/exercises/collection_basics.rbs +6 -0
- data/sig/exercises/collection_basics_test.rbs +4 -0
- data/sig/exercises/constant_scope.rbs +11 -0
- data/sig/exercises/core_protocols.rbs +20 -0
- data/sig/exercises/delegation.rbs +14 -0
- data/sig/exercises/dynamic_methods.rbs +13 -0
- data/sig/exercises/exception_flow.rbs +11 -0
- data/sig/exercises/heredoc_layout.rbs +11 -0
- data/sig/exercises/keyword_forwarding.rbs +12 -0
- data/sig/exercises/lazy_enumeration.rbs +10 -0
- data/sig/exercises/literals_and_operators.rbs +12 -0
- data/sig/exercises/method_contracts.rbs +11 -0
- data/sig/exercises/method_lookup.rbs +16 -0
- data/sig/exercises/nonlocal_exit.rbs +14 -0
- data/sig/exercises/numbered_parameters.rbs +11 -0
- data/sig/exercises/numeric_precision.rbs +11 -0
- data/sig/exercises/pattern_matching.rbs +10 -0
- data/sig/exercises/refinement_scope.rbs +18 -0
- data/sig/exercises/regexp_captures.rbs +11 -0
- data/sig/exercises/runtime_context.rbs +23 -0
- data/sig/exercises/shared_rows.rbs +11 -0
- data/sig/exercises/singleton_pattern.rbs +14 -0
- data/sig/exercises/time_and_date.rbs +11 -0
- data/sig/exercises/visibility.rbs +18 -0
- data/sig/fixture_support.rbs +11 -0
- data/sig/help_hint_command_test.rbs +8 -0
- data/sig/heredoc_layout_acceptance_test.rbs +3 -0
- data/sig/keyboard_session_test.rbs +9 -0
- data/sig/keyword_forwarding_acceptance_test.rbs +3 -0
- data/sig/lazy_enumeration_acceptance_test.rbs +3 -0
- data/sig/literals_and_operators_acceptance_test.rbs +3 -0
- data/sig/method_contracts_acceptance_test.rbs +3 -0
- data/sig/method_lookup_acceptance_test.rbs +3 -0
- data/sig/nonlocal_exit_acceptance_test.rbs +3 -0
- data/sig/numbered_parameters_acceptance_test.rbs +3 -0
- data/sig/numeric_precision_acceptance_test.rbs +4 -0
- data/sig/packaging_test.rbs +3 -0
- data/sig/pattern_matching_acceptance_test.rbs +3 -0
- data/sig/reference_solutions_test.rbs +3 -0
- data/sig/refinement_scope_acceptance_test.rbs +3 -0
- data/sig/regexp_captures_acceptance_test.rbs +4 -0
- data/sig/rkoans/catalog.rbs +11 -0
- data/sig/rkoans/checker.rbs +22 -0
- data/sig/rkoans/cli.rbs +21 -0
- data/sig/rkoans/exercise.rbs +18 -0
- data/sig/rkoans/keyboard.rbs +13 -0
- data/sig/rkoans/progress.rbs +9 -0
- data/sig/rkoans/selection.rbs +9 -0
- data/sig/rkoans/session.rbs +30 -0
- data/sig/rkoans/terminal_view.rbs +24 -0
- data/sig/rkoans/watcher.rbs +18 -0
- data/sig/rkoans.rbs +2 -0
- data/sig/runtime_context_command_test.rbs +4 -0
- data/sig/selection_command_test.rbs +5 -0
- data/sig/session_command_test.rbs +12 -0
- data/sig/session_edge_cases_test.rbs +4 -0
- data/sig/shared_rows_acceptance_test.rbs +3 -0
- data/sig/singleton_pattern_acceptance_test.rbs +3 -0
- data/sig/time_and_date_acceptance_test.rbs +4 -0
- data/sig/visibility_acceptance_test.rbs +4 -0
- data/sig/watcher_test.rbs +3 -0
- data/solutions/about-array-assignment/about_array_assignment.rb +36 -0
- data/solutions/about-arrays/about_arrays.rb +53 -0
- data/solutions/about-asserts/about_asserts.rb +22 -0
- data/solutions/about-blocks/about_blocks.rb +30 -0
- data/solutions/about-class-methods/about_class_methods.rb +36 -0
- data/solutions/about-classes/about_classes.rb +40 -0
- data/solutions/about-constants/about_constants.rb +26 -0
- data/solutions/about-control-statements/about_control_statements.rb +35 -0
- data/solutions/about-dice-project/about_dice_project.rb +27 -0
- data/solutions/about-exceptions/about_exceptions.rb +30 -0
- data/solutions/about-extra-credit/about_extra_credit.rb +114 -0
- data/solutions/about-hashes/about_hashes.rb +52 -0
- data/solutions/about-inheritance/about_inheritance.rb +27 -0
- data/solutions/about-iteration/about_iteration.rb +31 -0
- data/solutions/about-keyword-arguments/about_keyword_arguments.rb +28 -0
- data/solutions/about-message-passing/about_message_passing.rb +38 -0
- data/solutions/about-methods/about_methods.rb +39 -0
- data/solutions/about-modules/about_modules.rb +23 -0
- data/solutions/about-nil/about_nil.rb +23 -0
- data/solutions/about-objects/about_objects.rb +34 -0
- data/solutions/about-open-classes/about_open_classes.rb +23 -0
- data/solutions/about-pattern-matching/about_pattern_matching.rb +39 -0
- data/solutions/about-proxy-object-project/about_proxy_object_project.rb +44 -0
- data/solutions/about-regular-expressions/about_regular_expressions.rb +60 -0
- data/solutions/about-sandwich-code/about_sandwich_code.rb +60 -0
- data/solutions/about-scope/about_scope.rb +33 -0
- data/solutions/about-scoring-project/about_scoring_project.rb +19 -0
- data/solutions/about-strings/about_strings.rb +56 -0
- data/solutions/about-symbols/about_symbols.rb +35 -0
- data/solutions/about-to-str/about_to_str.rb +24 -0
- data/solutions/about-triangle-project/about_triangle_project.rb +19 -0
- data/solutions/about-triangle-project-two/about_triangle_project_two.rb +25 -0
- data/solutions/about-true-and-false/about_true_and_false.rb +25 -0
- data/solutions/blocks-and-methods/blocks_and_methods.rb +14 -0
- data/solutions/class-state/class_state.rb +33 -0
- data/solutions/closure-capture/closure_capture.rb +12 -0
- data/solutions/collection-basics/collection_basics.rb +14 -0
- data/solutions/constant-scope/constant_scope.rb +18 -0
- data/solutions/core-protocols/core_protocols.rb +44 -0
- data/solutions/delegation/delegation.rb +17 -0
- data/solutions/dynamic-methods/dynamic_methods.rb +32 -0
- data/solutions/exception-flow/exception_flow.rb +34 -0
- data/solutions/heredoc-layout/heredoc_layout.rb +24 -0
- data/solutions/keyword-forwarding/keyword_forwarding.rb +26 -0
- data/solutions/lazy-enumeration/lazy_enumeration.rb +11 -0
- data/solutions/literals-and-operators/literals_and_operators.rb +22 -0
- data/solutions/method-contracts/method_contracts.rb +18 -0
- data/solutions/method-lookup/method_lookup.rb +26 -0
- data/solutions/nonlocal-exit/nonlocal_exit.rb +46 -0
- data/solutions/numbered-parameters/numbered_parameters.rb +18 -0
- data/solutions/numeric-precision/numeric_precision.rb +17 -0
- data/solutions/pattern-matching/pattern_matching.rb +17 -0
- data/solutions/refinement-scope/refinement_scope.rb +34 -0
- data/solutions/regexp-captures/regexp_captures.rb +24 -0
- data/solutions/runtime-context/runtime_context.rb +19 -0
- data/solutions/shared-rows/shared_rows.rb +13 -0
- data/solutions/singleton-pattern/singleton_pattern.rb +19 -0
- data/solutions/time-and-date/time_and_date.rb +16 -0
- data/solutions/visibility/visibility.rb +34 -0
- data/test/exercises/about_array_assignment_test.rb +87 -0
- data/test/exercises/about_arrays_test.rb +119 -0
- data/test/exercises/about_asserts_test.rb +41 -0
- data/test/exercises/about_blocks_test.rb +140 -0
- data/test/exercises/about_class_methods_test.rb +232 -0
- data/test/exercises/about_classes_test.rb +261 -0
- data/test/exercises/about_constants_test.rb +111 -0
- data/test/exercises/about_control_statements_test.rb +188 -0
- data/test/exercises/about_dice_project_test.rb +40 -0
- data/test/exercises/about_exceptions_test.rb +98 -0
- data/test/exercises/about_extra_credit_test.rb +74 -0
- data/test/exercises/about_hashes_test.rb +163 -0
- data/test/exercises/about_inheritance_test.rb +121 -0
- data/test/exercises/about_iteration_test.rb +148 -0
- data/test/exercises/about_keyword_arguments_test.rb +80 -0
- data/test/exercises/about_message_passing_test.rb +230 -0
- data/test/exercises/about_methods_test.rb +208 -0
- data/test/exercises/about_modules_test.rb +98 -0
- data/test/exercises/about_nil_test.rb +35 -0
- data/test/exercises/about_objects_test.rb +74 -0
- data/test/exercises/about_open_classes_test.rb +60 -0
- data/test/exercises/about_pattern_matching_test.rb +257 -0
- data/test/exercises/about_proxy_object_project_test.rb +75 -0
- data/test/exercises/about_regular_expressions_test.rb +243 -0
- data/test/exercises/about_sandwich_code_test.rb +82 -0
- data/test/exercises/about_scope_test.rb +123 -0
- data/test/exercises/about_scoring_project_test.rb +62 -0
- data/test/exercises/about_strings_test.rb +255 -0
- data/test/exercises/about_symbols_test.rb +120 -0
- data/test/exercises/about_to_str_test.rb +89 -0
- data/test/exercises/about_triangle_project_test.rb +29 -0
- data/test/exercises/about_triangle_project_two_test.rb +28 -0
- data/test/exercises/about_true_and_false_test.rb +43 -0
- data/test/exercises/blocks_and_methods_test.rb +22 -0
- data/test/exercises/class_state_test.rb +29 -0
- data/test/exercises/closure_capture_test.rb +29 -0
- data/test/exercises/collection_basics_test.rb +18 -0
- data/test/exercises/constant_scope_test.rb +18 -0
- data/test/exercises/core_protocols_test.rb +28 -0
- data/test/exercises/delegation_test.rb +23 -0
- data/test/exercises/dynamic_methods_test.rb +28 -0
- data/test/exercises/exception_flow_test.rb +17 -0
- data/test/exercises/heredoc_layout_test.rb +18 -0
- data/test/exercises/keyword_forwarding_test.rb +22 -0
- data/test/exercises/lazy_enumeration_test.rb +15 -0
- data/test/exercises/literals_and_operators_test.rb +25 -0
- data/test/exercises/method_contracts_test.rb +28 -0
- data/test/exercises/method_lookup_test.rb +14 -0
- data/test/exercises/nonlocal_exit_test.rb +22 -0
- data/test/exercises/numbered_parameters_test.rb +15 -0
- data/test/exercises/numeric_precision_test.rb +24 -0
- data/test/exercises/pattern_matching_test.rb +18 -0
- data/test/exercises/refinement_scope_test.rb +14 -0
- data/test/exercises/regexp_captures_test.rb +20 -0
- data/test/exercises/runtime_context_test.rb +59 -0
- data/test/exercises/shared_rows_test.rb +34 -0
- data/test/exercises/singleton_pattern_test.rb +16 -0
- data/test/exercises/time_and_date_test.rb +22 -0
- data/test/exercises/visibility_test.rb +22 -0
- metadata +471 -4
- data/Rakefile +0 -12
- 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,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,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,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
|
data/lib/rkoans/error.rb
ADDED
|
@@ -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
|