diff --git a/koans/about_regular_expressions.rb b/koans/about_regular_expressions.rb new file mode 100644 index 0000000..417cca5 --- /dev/null +++ b/koans/about_regular_expressions.rb @@ -0,0 +1,158 @@ +require File.expand_path(File.dirname(__FILE__) + '/edgecase') + +class AboutRegularExpressions < EdgeCase::Koan + def test_a_pattern_is_a_regular_expression + assert_equal Regexp, /pattern/.class + end + + def test_a_regexp_can_search_a_string_for_matching_content + assert_equal "match", "some matching content"[/match/] + end + + def test_a_failed_match_returns_nil + assert_equal __, "some matching content"[/missing/] + end + + # ------------------------------------------------------------------ + + def test_question_mark_means_optional + assert_equal __, "abbcccddddeeeee"[/ab?/] + assert_equal __, "abbcccddddeeeee"[/az?/] + end + + def test_plus_means_one_or_more + assert_equal __, "abbcccddddeeeee"[/bc+/] + end + + def test_asterisk_means_zero_or_more + assert_equal __, "abbcccddddeeeee"[/ab*/] + assert_equal __, "abbcccddddeeeee"[/az*/] + assert_equal __, "abbcccddddeeeee"[/z*/] + + # THINK ABOUT IT: + # + # When would * fail to match? + end + + # THINK ABOUT IT: + # + # We say that the repition operators above are "greedy." + # + # Why? + + # ------------------------------------------------------------------ + + def test_the_left_most_match_wins + assert_equal __, "abbccc az"[/az*/] + end + + # ------------------------------------------------------------------ + + def test_character_classes_give_options_for_a_character + animals = ["cat", "bat", "rat", "zat"] + assert_equal __, animals.select { |a| a[/[cbr]at/] } + end + + def test_slash_d_is_a_shortcut_for_a_digit_character_class + assert_equal __, "the number is 42"[/[0123456789]+/] + assert_equal __, "the number is 42"[/\d+/] + end + + def test_character_classes_can_include_ranges + assert_equal __, "the number is 42"[/[0-9]+/] + end + + def test_slash_s_is_a_shortcut_for_a_whitespace_character_class + assert_equal __, "space: \t\n"[/\s+/] + end + + def test_slash_w_is_a_shortcut_for_a_word_character_class + # NOTE: This is more like how a programmer might define a word. + assert_equal __, "variable_1 = 42"[/[a-zA-Z0-9_]+/] + assert_equal __, "variable_1 = 42"[/\w+/] + end + + def test_period_is_a_shortcut_for_any_non_newline_character + assert_equal __, "abc\n123"[/a.+/] + end + + def test_a_character_class_can_be_negated + assert_equal __, "the number is 42"[/[^0-9]+/] + end + + def test_shortcut_character_classes_are_negated_with_capitals + assert_equal __, "the number is 42"[/\D+/] + assert_equal __, "space: \t\n"[/\S+/] + assert_equal __, "variable_1 = 42"[/\W+/] + end + + # ------------------------------------------------------------------ + + def test_slash_a_anchors_to_the_start_of_the_string + assert_equal __, "start end"[/\Astart/] + assert_equal __, "start end"[/\Aend/] + end + + def test_slash_z_anchors_to_the_end_of_the_string + assert_equal __, "start end"[/end\z/] + assert_equal __, "start end"[/start\z/] + end + + def test_caret_anchors_to_the_start_of_lines + assert_equal __, "num 42\n2 lines"[/^\d+/] + end + + def test_dollar_sign_anchors_to_the_end_of_lines + assert_equal __, "2 lines\nnum 42"[/\d+$/] + end + + def test_slash_b_anchors_to_a_word_boundary + assert_equal __, "bovine vines"[/\bvine./] + end + + # ------------------------------------------------------------------ + + def test_parentheses_group_contents + assert_equal __, "ahahaha"[/(ha)+/] + end + + # ------------------------------------------------------------------ + + def test_parentheses_also_capture_matched_content_by_number + assert_equal __, "Gray, James"[/(\w+), (\w+)/, 1] + assert_equal __, "Gray, James"[/(\w+), (\w+)/, 2] + end + + def test_variables_can_also_be_used_to_access_captures + assert_equal __, "Name: Gray, James"[/(\w+), (\w+)/] + assert_equal __, $1 + assert_equal __, $2 + end + + # ------------------------------------------------------------------ + + def test_a_vertical_pipe_means_or + grays = /(James|Dana|Summer) Gray/ + assert_equal __, "James Gray"[grays] + assert_equal __, "Summer Gray"[grays, 1] + assert_equal __, "Jim Gray"[grays, 1] + end + + # THINK ABOUT IT: + # + # Explain the difference between a character class ([…]) and alternation (|). + + # ------------------------------------------------------------------ + + def test_scan_is_like_find_all + assert_equal __, "one two-three".scan(/\w+/) + end + + def test_sub_is_like_find_and_replace + assert_equal __, "one two-three".sub(/(t\w*)/) { $1[0, 1] } + end + + def test_gsub_is_like_find_and_replace_all + assert_equal __, "one two-three".gsub(/(t\w*)/) { $1[0, 1] } + end +end diff --git a/koans/about_strings.rb b/koans/about_strings.rb index 87bb4ec..f26e699 100644 --- a/koans/about_strings.rb +++ b/koans/about_strings.rb @@ -174,8 +174,8 @@ EOS assert_equal [__, __, __, __], words # NOTE: Patterns are formed from Regular Expressions. Ruby has a - # very powerful Regular Expression library. Unfortunately, time - # does not permit us to explore it in detail now. + # very powerful Regular Expression library. We will become + # enlightened about them soon. end def test_strings_can_be_joined diff --git a/koans/path_to_enlightenment.rb b/koans/path_to_enlightenment.rb index ee57759..2e12c46 100644 --- a/koans/path_to_enlightenment.rb +++ b/koans/path_to_enlightenment.rb @@ -8,6 +8,7 @@ require 'about_arrays' require 'about_array_assignment' require 'about_hashes' require 'about_strings' +require 'about_regular_expressions' require 'about_methods' require 'about_constants' require 'about_control_statements' diff --git a/src/about_regular_expressions.rb b/src/about_regular_expressions.rb new file mode 100644 index 0000000..967bc6a --- /dev/null +++ b/src/about_regular_expressions.rb @@ -0,0 +1,158 @@ +require File.expand_path(File.dirname(__FILE__) + '/edgecase') + +class AboutRegularExpressions < EdgeCase::Koan + def test_a_pattern_is_a_regular_expression + assert_equal Regexp, /pattern/.class + end + + def test_a_regexp_can_search_a_string_for_matching_content + assert_equal "match", "some matching content"[/match/] + end + + def test_a_failed_match_returns_nil + assert_equal __(nil), "some matching content"[/missing/] + end + + # ------------------------------------------------------------------ + + def test_question_mark_means_optional + assert_equal __("ab"), "abbcccddddeeeee"[/ab?/] + assert_equal __("a"), "abbcccddddeeeee"[/az?/] + end + + def test_plus_means_one_or_more + assert_equal __("bccc"), "abbcccddddeeeee"[/bc+/] + end + + def test_asterisk_means_zero_or_more + assert_equal __("abb"), "abbcccddddeeeee"[/ab*/] + assert_equal __("a"), "abbcccddddeeeee"[/az*/] + assert_equal __(""), "abbcccddddeeeee"[/z*/] + + # THINK ABOUT IT: + # + # When would * fail to match? + end + + # THINK ABOUT IT: + # + # We say that the repition operators above are "greedy." + # + # Why? + + # ------------------------------------------------------------------ + + def test_the_left_most_match_wins + assert_equal __("a"), "abbccc az"[/az*/] + end + + # ------------------------------------------------------------------ + + def test_character_classes_give_options_for_a_character + animals = ["cat", "bat", "rat", "zat"] + assert_equal __(["cat", "bat", "rat"]), animals.select { |a| a[/[cbr]at/] } + end + + def test_slash_d_is_a_shortcut_for_a_digit_character_class + assert_equal __("42"), "the number is 42"[/[0123456789]+/] + assert_equal __("42"), "the number is 42"[/\d+/] + end + + def test_character_classes_can_include_ranges + assert_equal __("42"), "the number is 42"[/[0-9]+/] + end + + def test_slash_s_is_a_shortcut_for_a_whitespace_character_class + assert_equal __(" \t\n"), "space: \t\n"[/\s+/] + end + + def test_slash_w_is_a_shortcut_for_a_word_character_class + # NOTE: This is more like how a programmer might define a word. + assert_equal __("variable_1"), "variable_1 = 42"[/[a-zA-Z0-9_]+/] + assert_equal __("variable_1"), "variable_1 = 42"[/\w+/] + end + + def test_period_is_a_shortcut_for_any_non_newline_character + assert_equal __("abc"), "abc\n123"[/a.+/] + end + + def test_a_character_class_can_be_negated + assert_equal __("the number is "), "the number is 42"[/[^0-9]+/] + end + + def test_shortcut_character_classes_are_negated_with_capitals + assert_equal __("the number is "), "the number is 42"[/\D+/] + assert_equal __("space:"), "space: \t\n"[/\S+/] + assert_equal __(" = "), "variable_1 = 42"[/\W+/] + end + + # ------------------------------------------------------------------ + + def test_slash_a_anchors_to_the_start_of_the_string + assert_equal __("start"), "start end"[/\Astart/] + assert_equal __(nil), "start end"[/\Aend/] + end + + def test_slash_z_anchors_to_the_end_of_the_string + assert_equal __("end"), "start end"[/end\z/] + assert_equal __(nil), "start end"[/start\z/] + end + + def test_caret_anchors_to_the_start_of_lines + assert_equal __("2"), "num 42\n2 lines"[/^\d+/] + end + + def test_dollar_sign_anchors_to_the_end_of_lines + assert_equal __("42"), "2 lines\nnum 42"[/\d+$/] + end + + def test_slash_b_anchors_to_a_word_boundary + assert_equal __("vines"), "bovine vines"[/\bvine./] + end + + # ------------------------------------------------------------------ + + def test_parentheses_group_contents + assert_equal __("hahaha"), "ahahaha"[/(ha)+/] + end + + # ------------------------------------------------------------------ + + def test_parentheses_also_capture_matched_content_by_number + assert_equal __("Gray"), "Gray, James"[/(\w+), (\w+)/, 1] + assert_equal __("James"), "Gray, James"[/(\w+), (\w+)/, 2] + end + + def test_variables_can_also_be_used_to_access_captures + assert_equal __("Gray, James"), "Name: Gray, James"[/(\w+), (\w+)/] + assert_equal __("Gray"), $1 + assert_equal __("James"), $2 + end + + # ------------------------------------------------------------------ + + def test_a_vertical_pipe_means_or + grays = /(James|Dana|Summer) Gray/ + assert_equal __("James Gray"), "James Gray"[grays] + assert_equal __("Summer"), "Summer Gray"[grays, 1] + assert_equal __(nil), "Jim Gray"[grays, 1] + end + + # THINK ABOUT IT: + # + # Explain the difference between a character class ([…]) and alternation (|). + + # ------------------------------------------------------------------ + + def test_scan_is_like_find_all + assert_equal __(["one", "two", "three"]), "one two-three".scan(/\w+/) + end + + def test_sub_is_like_find_and_replace + assert_equal __("one t-three"), "one two-three".sub(/(t\w*)/) { $1[0, 1] } + end + + def test_gsub_is_like_find_and_replace_all + assert_equal __("one t-t"), "one two-three".gsub(/(t\w*)/) { $1[0, 1] } + end +end diff --git a/src/about_strings.rb b/src/about_strings.rb index 106ba79..94211a9 100644 --- a/src/about_strings.rb +++ b/src/about_strings.rb @@ -174,8 +174,8 @@ EOS assert_equal [__("the"), __("rain"), __("in"), __("spain")], words # NOTE: Patterns are formed from Regular Expressions. Ruby has a - # very powerful Regular Expression library. Unfortunately, time - # does not permit us to explore it in detail now. + # very powerful Regular Expression library. We will become + # enlightened about them soon. end def test_strings_can_be_joined diff --git a/src/path_to_enlightenment.rb b/src/path_to_enlightenment.rb index ee57759..2e12c46 100644 --- a/src/path_to_enlightenment.rb +++ b/src/path_to_enlightenment.rb @@ -8,6 +8,7 @@ require 'about_arrays' require 'about_array_assignment' require 'about_hashes' require 'about_strings' +require 'about_regular_expressions' require 'about_methods' require 'about_constants' require 'about_control_statements'