From 0f7f5c18055b76890d9038787818622eccd5113b Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 19 Apr 2018 00:44:59 +0200 Subject: [PATCH] Add Regex.needs_escape? --- spec/std/regex_spec.cr | 11 +++++++++++ src/regex.cr | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr index f97c21448813..419b6988c323 100644 --- a/spec/std/regex_spec.cr +++ b/spec/std/regex_spec.cr @@ -45,6 +45,17 @@ describe "Regex" do expect_raises(ArgumentError) { Regex.new("foo)") } end + it "checks if Char need to be escaped" do + Regex.needs_escape?('*').should be_true + Regex.needs_escape?('|').should be_true + Regex.needs_escape?('@').should be_false + end + + it "checks if String need to be escaped" do + Regex.needs_escape?("10$").should be_true + Regex.needs_escape?("foo").should be_false + end + it "escapes" do Regex.escape(" .\\+*?[^]$(){}=!<>|:-hello").should eq("\\ \\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:\\-hello") end diff --git a/src/regex.cr b/src/regex.cr index 741c9a1935b8..20cb9aa3da12 100644 --- a/src/regex.cr +++ b/src/regex.cr @@ -192,6 +192,15 @@ require "./regex/*" # `Hash` of `String` => `Int32`, and therefore requires named capture groups to have # unique names within a single `Regex`. class Regex + # List of metacharacters that need to be escaped. + # + # See `Regex.needs_escape?` and `Regex.escape`. + SPECIAL_CHARACTERS = { + ' ', '.', '\\', '+', '*', '?', '[', '^', ']', + '$', '(', ')', '{', '}', '=', '!', '<', '>', + '|', ':', '-', + } + @[Flags] enum Options IGNORE_CASE = 1 @@ -265,6 +274,27 @@ class Regex end end + # Returns `true` if *char* need to be escaped, `false` otherwise. + # + # ``` + # Regex.needs_escape?('*') # => true + # Regex.needs_escape?('@') # => false + # ``` + def self.needs_escape?(char : Char) : Bool + SPECIAL_CHARACTERS.includes?(char) + end + + # Returns `true` if *str* need to be escaped, `false` otherwise. + # + # ``` + # Regex.needs_escape?("10$") # => true + # Regex.needs_escape?("foo") # => false + # ``` + def self.needs_escape?(str : String) : Bool + str.each_char { |char| return true if SPECIAL_CHARACTERS.includes?(char) } + false + end + # Returns a `String` constructed by escaping any metacharacters in *str*. # # ``` @@ -274,15 +304,8 @@ class Regex def self.escape(str) : String String.build do |result| str.each_byte do |byte| - case byte.unsafe_chr - when ' ', '.', '\\', '+', '*', '?', '[', - '^', ']', '$', '(', ')', '{', '}', - '=', '!', '<', '>', '|', ':', '-' - result << '\\' - result.write_byte byte - else - result.write_byte byte - end + result << '\\' if needs_escape?(byte.unsafe_chr) + result.write_byte byte end end end