Created
April 1, 2017 20:39
-
-
Save apeiros/1d947d0be61001169b9b5b1edd996865 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Regexp | |
def self.quantifier(min,max) | |
if !max | |
if min == 0 | |
"*" | |
elsif min == 1 | |
"+" | |
else | |
raise "Invalid" | |
end | |
elsif min == max | |
if min == 1 | |
"" | |
else | |
"{#{min}}" | |
end | |
elsif min == 0 && max == 1 | |
"?" | |
else | |
"{#{min},#{max}}" | |
end | |
end | |
def self.char_class(from,to) | |
from = from.to_s | |
to = to.to_s | |
if from == to | |
from | |
elsif from == "0".freeze && to == "9".freeze | |
"\\d".freeze | |
else | |
"[#{from}-#{to}]" | |
end | |
end | |
# Generate a regex matching a number from zero to N | |
# Like Regexp.natural_numbers_up_to(255) =~ "254" | |
# | |
# Setting anchor to nil will use its default (:string) | |
# Possible values for anchor: | |
# * :none - alternatively `false`, the number is not anchored. Useful when | |
# embedding into a larger regex. But also check :word for that. | |
# * :string - alternatively `true`, the number makes up the full string (uses | |
# \A and \z) | |
# * :line - the number makes up one line (uses ^ and $) | |
# * :word - the number is a word in a string (uses (?<!)\d and (?!)\d | |
# lookarounds) | |
# * [before, after] - if anchor is an array, the first value of the array is | |
# put at the front, the the last value at the end of the regex, both | |
# unescaped | |
# | |
# @example Match an IPv4 address | |
# segment = Regexp.natural_numbers_up_to(255, anchor: false) | |
# ipv4 = /\A#{segment}(?:\.#{segment}){3}\z/ | |
def self.natural_numbers_up_to(value, anchor: nil) | |
digits = value.to_s.bytesize | |
if digits > 1 | |
value_str = value.to_s | |
ldigits = digits - 1 | |
components = ["\\d"] | |
components << "[1-9]\\d#{quantifier(1, digits-2)}" if digits > 2 | |
components << "#{char_class(1,value_str[0].to_i-1)}\\d#{quantifier(ldigits, ldigits)}" if value_str[0].to_i > 1 | |
1.upto(digits-2) do |i| | |
cdigit = value_str[i].to_i | |
if cdigit > 0 | |
components << "#{value_str[0, i]}#{char_class(0, cdigit - 1)}\\d#{quantifier(ldigits-i, ldigits-i)}" | |
end | |
end | |
components << "#{value_str[0..-2]}#{char_class("0", value_str[-1])}" | |
base_regex = components.join("|") | |
else | |
base_regex = char_class(0, value) | |
end | |
case anchor | |
when :string, nil, true then /\A(?:#{base_regex})\z/ | |
when :none, false then /(?:#{base_regex})/ | |
when :word then /(?<!)\d(?:#{base_regex})(?!)\d/ | |
when :line then /^(?:#{base_regex})$/ | |
when Array then /#{anchor.first}(?:#{base_regex})#{anchor.last}/ | |
else raise "Invalid value for anchor" | |
end | |
end | |
end | |
# (0..9999).map { |max| p max if max%100==0; re = Regexp.natural_numbers_up_to(max, anchor: :string); [re, (0..max).reject { |v| v.to_s =~ re }] }.reject { |re,v| v.empty? }.empty? | |
# Regexp.natural_numbers_up_to(0) | |
# Regexp.natural_numbers_up_to(5) | |
# Regexp.natural_numbers_up_to(10) | |
# Regexp.natural_numbers_up_to(16) | |
# Regexp.natural_numbers_up_to(50) | |
# Regexp.natural_numbers_up_to(56) | |
# Regexp.natural_numbers_up_to(100) | |
# Regexp.natural_numbers_up_to(255) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment