diff --git a/lib/string_tools/core_ext/string.rb b/lib/string_tools/core_ext/string.rb index cd35425..68ae152 100644 --- a/lib/string_tools/core_ext/string.rb +++ b/lib/string_tools/core_ext/string.rb @@ -39,12 +39,119 @@ def naturalized scan(/[^\d\.]+|[\d\.]+/).map{|f| f.match(/^\d+(\.\d+)?$/) ? f.to_f : f } end + def self.natcmp(str1, str2) + str1, str2 = str1.dup, str2.dup + compare_expression = /^(\D*)((?:\d+(?:\.\d+)?)*)(.*)$/ + + # Remove all whitespace + str1.gsub!(/\s*/, '') + str2.gsub!(/\s*/, '') + + while (str1.length > 0) or (str2.length > 0) + # Extract non-digits, digits and rest of string + str1 =~ compare_expression + chars1, num1, str1 = $1.dup, $2.dup, $3.dup + + str2 =~ compare_expression + chars2, num2, str2 = $1.dup, $2.dup, $3.dup + + # Compare the non-digits + case (chars1 <=> chars2) + when 0 # Non-digits are the same, compare the digits... + # If either number begins with a zero, then compare alphabetically, + # otherwise compare numerically + if !(num1[0] == 48 && num1[1] != 46) and !(num2[0] == 48 && num2[1] != 46) + num1, num2 = num1.to_f, num2.to_f + end + + case (num1 <=> num2) + when -1 then return -1 + when 1 then return 1 + end + when -1 then return -1 + when 1 then return 1 + end # case + + end # while + + # Strings are naturally equal + 0 + end # Выполняет преобразование строки в punycode. def to_punycode Addressable::URI.parse(self).normalize.to_s end + # Embed in a String to clear all previous ANSI sequences. + ANSI_CLEAR = "\e[0m" + ANSI_BOLD = "\e[1m" + ANSI_UNDERLINE = "\e[4m" + + # Colors + BLACK = "\e[30m" + RED = "\e[31m" + GREEN = "\e[32m" + YELLOW = "\e[33m" + BLUE = "\e[34m" + MAGENTA = "\e[35m" + CYAN = "\e[36m" + WHITE = "\e[37m" + + # === Synopsys + # Colorize string (for terminals) + # Does not work with sprintf yet + # + # === Usage + # "ln -s".colorize(:red) + # + # === Args + # +color+ - symbol, one of the following (black, white, red, green, yellow, blue, magenta, cyan) + # +bold_or_options+ - True/False or Hash + def colorize(color, bold_or_options = nil) + is_bold = bold_or_options.is_a?(TrueClass) + is_underline = false + + if bold_or_options.is_a?(Hash) + is_bold ||= bold_or_options[:bold] + is_underline = bold_or_options[:underline] + end + + raise ArgumentError('Color must be a symbol') unless color.is_a?(Symbol) + color_const = color.to_s.upcase.to_sym + + raise ArgumentError('Unknown color') unless self.class.const_defined?(color_const) + ascii_color = self.class.const_get(color_const) + + s = surround_with_ansi(ascii_color) + s = s.bold if is_bold + s = s.underline if is_underline + s + end + + # === Synopsys + # Make text bolder (for ASCII terminals) + def bold + surround_with_ansi(ANSI_BOLD) + end + + # === Synopsys + # Make text underlined (for ASCII terminals) + def underline + surround_with_ansi(ANSI_UNDERLINE) + end + + # === Synopsys + # remove colors from colorized string + def remove_colors + gsub(/\e\[\d+m/, '') + end + + [:black, :white, :red, :green, :yellow, :blue, :magenta, :cyan].each do |color| + define_method color do |*args| + colorize(color, *args) + end + end # shorthand def detect_encoding @@ -84,4 +191,14 @@ def to_cp1251 def to_cp1251! self.replace(self.to_cp1251) end + + private + + def surround_with_ansi(ascii_seq) + "#{ascii_seq}#{protect_escape_of(ascii_seq)}#{ANSI_CLEAR}" + end + + def protect_escape_of(ascii_seq) + gsub(Regexp.new(Regexp.escape(ANSI_CLEAR)), "#{ANSI_CLEAR}#{ascii_seq}") + end end