Skip to content

Instantly share code, notes, and snippets.

@apeiros
Created April 1, 2017 20:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apeiros/1d947d0be61001169b9b5b1edd996865 to your computer and use it in GitHub Desktop.
Save apeiros/1d947d0be61001169b9b5b1edd996865 to your computer and use it in GitHub Desktop.
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