class Mail::Multibyte::Chars

Mail::Multibyte.proxy_class = CharsForUTF32
end
end
string.length % 4 == 0
def self.accepts?(string)
end
@wrapped_string.size / 4
def size
class CharsForUTF32
Mail::Multibyte.proxy_class.
encodings you can write your own multibyte string handler and configure it through
The default Chars implementation assumes that the encoding of the string is UTF-8, if you want to handle different
bad.explicit_checking_method “T”.mb_chars.downcase.to_s
If certain methods do explicitly check the class, call to_s before you pass chars objects to them.
Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made.
“The Perfect String ”.mb_chars.downcase.strip.normalize # => “the perfect string”
which would normally return a String object now return a Chars object so methods can be chained.
String methods are proxied through the Chars object, and can be accessed through the mb_chars method. Methods
encoding safe manner. All the normal String methods are also implemented on the proxy.
knowledge about the encoding. A Chars object accepts a string upon initialization and proxies String methods in an
Chars enables you to work transparently with UTF-8 encoding in the Ruby String class without having extensive
:nodoc:
:nodoc:

def self.consumes?(string)

Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
def self.consumes?(string)
  # Unpack is a little bit faster than regular expressions.
  string.unpack('U*')
  true
rescue ArgumentError
  false
end

def self.wants?(string)

+false+ otherwise.
Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
def self.wants?(string)
  $KCODE == 'UTF8' && consumes?(string)
end

def +(other)

('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"
Example:

Returns a new Chars object containing the _other_ object concatenated to the string.
def +(other)
  chars(@wrapped_string + other)
end

def <=>(other)

See String#<=> for more details.

'é'.mb_chars <=> 'ü'.mb_chars # => -1

that implements +to_s+:
equal or after the object on the right side of the operation. It accepts any object
Returns -1, 0, or 1, depending on whether the Chars object is to be sorted before,
def <=>(other)
  @wrapped_string <=> other.to_s
end

def =~(other)

'Café périferôl'.mb_chars =~ /ô/ # => 12
Example:

Like String#=~ only it returns the character offset (in codepoints) instead of the byte offset.
def =~(other)
  translate_offset(@wrapped_string =~ other)
end

def =~(other)

def =~(other)
  @wrapped_string =~ other
end

def []=(*args)

# => "Möler"
s
s.mb_chars[1, 2] = "ö" # Replace 2 characters at character offset 1
s = "Müller"

# => "Müeler"
s
s.mb_chars[2] = "e" # Replace character with offset 2
s = "Müller"

Example:

Like String#[]=, except instead of byte offsets you specify character offsets.
def []=(*args)
  replace_by = args.pop
  # Indexed replace with regular expressions already works
  if args.first.is_a?(Regexp)
    @wrapped_string[*args] = replace_by
  else
    result = Unicode.u_unpack(@wrapped_string)
    if args[0].is_a?(Fixnum)
      raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
      min = args[0]
      max = args[1].nil? ? min : (min + args[1] - 1)
      range = Range.new(min, max)
      replace_by = [replace_by].pack('U') if replace_by.is_a?(Fixnum)
    elsif args.first.is_a?(Range)
      raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
      range = args[0]
    else
      needle = args[0].to_s
      min = index(needle)
      max = min + Unicode.u_unpack(needle).length - 1
      range = Range.new(min, max)
    end
    result[range] = Unicode.u_unpack(replace_by)
    @wrapped_string.replace(result.pack('U*'))
  end
end

def acts_like_string?

Enable more predictable duck-typing on String-like classes. See Object#acts_like?.
def acts_like_string?
  true
end

def capitalize

'über'.mb_chars.capitalize.to_s # => "Über"
Example:

Converts the first character to uppercase and the remainder to lowercase.
def capitalize
  (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
end

def center(integer, padstr=' ')

# => " ¾ cup  "
"¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace

# => " ¾ cup "
"¾ cup".mb_chars.center(8).to_s

Example:

Works just like String#center, only integer specifies characters instead of bytes.
def center(integer, padstr=' ')
  justify(integer, :center, padstr)
end

def chars(string) #:nodoc:

:nodoc:
def chars(string) #:nodoc:
  self.class.new(string)
end

def compose

'é'.mb_chars.compose.to_s.length # => 2
'é'.length # => 3
Example:

Performs composition on all the characters.
def compose
  chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
end

def decompose

'é'.mb_chars.decompose.to_s.length # => 3
'é'.length # => 2
Example:

Performs canonical decomposition on all the characters.
def decompose
  chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
end

def downcase

'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
Example:

Convert characters in the string to lowercase.
def downcase
  chars(Unicode.apply_mapping(@wrapped_string, :lowercase_mapping))
end

def g_length

'क्षि'.mb_chars.g_length # => 3
'क्षि'.mb_chars.length # => 4
Example:

Returns the number of grapheme clusters in the string.
def g_length
  Unicode.g_unpack(@wrapped_string).length
end

def include?(other)

'Café'.mb_chars.include?('é') # => true
Example:

Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
def include?(other)
  # We have to redefine this method because Enumerable defines it.
  @wrapped_string.include?(other)
end

def index(needle, offset=0)

'Café périferôl'.mb_chars.index(/\w/u) # => 0
'Café périferôl'.mb_chars.index('ô') # => 12
Example:

Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
def index(needle, offset=0)
  wrapped_offset = first(offset).wrapped_string.length
  index = @wrapped_string.index(needle, wrapped_offset)
  index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
end

def initialize(string)

Creates a new Chars instance by wrapping _string_.
def initialize(string)
  @wrapped_string = string
  @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
end

def initialize(string) #:nodoc:

:nodoc:
def initialize(string) #:nodoc:
  @wrapped_string = string
end

def insert(offset, fragment)

'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"
Example:

Inserts the passed string at specified codepoint offsets.
def insert(offset, fragment)
  unpacked = Unicode.u_unpack(@wrapped_string)
  unless offset > unpacked.length
    @wrapped_string.replace(
      Unicode.u_unpack(@wrapped_string).insert(offset, *Unicode.u_unpack(fragment)).pack('U*')
    )
  else
    raise IndexError, "index #{offset} out of string"
  end
  self
end

def justify(integer, way, padstr=' ') #:nodoc:

:nodoc:
def justify(integer, way, padstr=' ') #:nodoc:
  raise ArgumentError, "zero width padding" if padstr.length == 0
  padsize = integer - size
  padsize = padsize > 0 ? padsize : 0
  case way
  when :right
    result = @wrapped_string.dup.insert(0, padding(padsize, padstr))
  when :left
    result = @wrapped_string.dup.insert(-1, padding(padsize, padstr))
  when :center
    lpad = padding((padsize / 2.0).floor, padstr)
    rpad = padding((padsize / 2.0).ceil, padstr)
    result = @wrapped_string.dup.insert(0, lpad).insert(-1, rpad)
  end
  chars(result)
end

def limit(limit)

s.mb_chars.limit(7) # => "こに"
s = 'こんにちは'
Example:

when the storage for a string is limited for some reason.
Limit the byte size of the string to a number of bytes without breaking characters. Usable
def limit(limit)
  slice(0...translate_offset(limit))
end

def ljust(integer, padstr=' ')

# => "¾ cup   "
"¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace

# => "¾ cup "
"¾ cup".mb_chars.rjust(8).to_s

Example:

Works just like String#ljust, only integer specifies characters instead of bytes.
def ljust(integer, padstr=' ')
  justify(integer, :left, padstr)
end

def lstrip

Strips entire range of Unicode whitespace from the left of the string.
def lstrip
  chars(@wrapped_string.gsub(Unicode::LEADERS_PAT, ''))
end

def method_missing(method, *args, &block)

Forward all undefined methods to the wrapped string.
def method_missing(method, *args, &block)
  if method.to_s =~ /!$/
    @wrapped_string.__send__(method, *args, &block)
    self
  else
    result = @wrapped_string.__send__(method, *args, &block)
    result.kind_of?(String) ? chars(result) : result
  end
end

def normalize(form = nil)

Mail::Multibyte::Unicode.default_normalization_form
:c, :kc, :d, or :kd. Default is
* form - The form you want to normalize in. Should be one of the following:

passing strings to databases and validations.
Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
def normalize(form = nil)
  chars(Unicode.normalize(@wrapped_string, form))
end

def ord

'こんにちは'.mb_chars.ord # => 12371
Example:

Returns the codepoint of the first character in the string.
def ord
  Unicode.u_unpack(@wrapped_string)[0]
end

def padding(padsize, padstr=' ') #:nodoc:

:nodoc:
def padding(padsize, padstr=' ') #:nodoc:
  if padsize != 0
    chars(padstr * ((padsize / Unicode.u_unpack(padstr).size) + 1)).slice(0, padsize)
  else
    ''
  end
end

def respond_to?(method, include_private=false)

only if the optional second parameter evaluates to +true+.
Returns +true+ if _obj_ responds to the given method. Private methods are included in the search
def respond_to?(method, include_private=false)
  super || @wrapped_string.respond_to?(method, include_private) || false
end

def reverse

'Café'.mb_chars.reverse.to_s # => 'éfaC'
Example:

Reverses all characters in the string.
def reverse
  chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
end

def rindex(needle, offset=nil)

'Café périferôl'.mb_chars.rindex(/\w/u) # => 13
'Café périferôl'.mb_chars.rindex('é') # => 6
Example:

string. Returns +nil+ if _needle_ isn't found.
codepoints, searching backward from _offset_ or the end of the
Returns the position _needle_ in the string, counting in
def rindex(needle, offset=nil)
  offset ||= length
  wrapped_offset = first(offset).wrapped_string.length
  index = @wrapped_string.rindex(needle, wrapped_offset)
  index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
end

def rjust(integer, padstr=' ')

# => "   ¾ cup"
"¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace

# => " ¾ cup"
"¾ cup".mb_chars.rjust(8).to_s

Example:

Works just like String#rjust, only integer specifies characters instead of bytes.
def rjust(integer, padstr=' ')
  justify(integer, :right, padstr)
end

def rstrip

Strips entire range of Unicode whitespace from the right of the string.
def rstrip
  chars(@wrapped_string.gsub(Unicode::TRAILERS_PAT, ''))
end

def size

Returns the number of codepoints in the string
def size
  Unicode.u_unpack(@wrapped_string).size
end

def slice(*args)

'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
Example:

character.
Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that
def slice(*args)
  if args.size > 2
    raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
  elsif (args.size == 2 && !(args.first.is_a?(Numeric) || args.first.is_a?(Regexp)))
    raise TypeError, "cannot convert #{args.first.class} into Integer" # Do as if we were native
  elsif (args.size == 2 && !args[1].is_a?(Numeric))
    raise TypeError, "cannot convert #{args[1].class} into Integer" # Do as if we were native
  elsif args[0].kind_of? Range
    cps = Unicode.u_unpack(@wrapped_string).slice(*args)
    result = cps.nil? ? nil : cps.pack('U*')
  elsif args[0].kind_of? Regexp
    result = @wrapped_string.slice(*args)
  elsif args.size == 1 && args[0].kind_of?(Numeric)
    character = Unicode.u_unpack(@wrapped_string)[args[0]]
    result = character && [character].pack('U')
  else
    cps = Unicode.u_unpack(@wrapped_string).slice(*args)
    result = cps && cps.pack('U*')
  end
  result && chars(result)
end

def split(*args)

'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
Example:

instances instead of String. This makes chaining methods easier.
Works just like String#split, with the exception that the items in the resulting list are Chars
def split(*args)
  @wrapped_string.split(*args).map { |i| i.mb_chars }
end

def strip

Strips entire range of Unicode whitespace from the right and left of the string.
def strip
  rstrip.lstrip
end

def tidy_bytes(force = false)

Passing +true+ will forcibly tidy all bytes, assuming that the string's encoding is entirely CP1252 or ISO-8859-1.

Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
def tidy_bytes(force = false)
  chars(Unicode.tidy_bytes(@wrapped_string, force))
end

def titleize

"日本語".mb_chars.titleize # => "日本語"
"ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
Example:

Capitalizes the first letter of every word, when possible.
def titleize
  chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.apply_mapping $1, :uppercase_mapping })
end

def translate_offset(byte_offset) #:nodoc:

:nodoc:
def translate_offset(byte_offset) #:nodoc:
  return nil if byte_offset.nil?
  return 0   if @wrapped_string == ''
  if @wrapped_string.respond_to?(:force_encoding)
    @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT)
  end
  begin
    @wrapped_string[0...byte_offset].unpack('U*').length
  rescue ArgumentError
    byte_offset -= 1
    retry
  end
end

def upcase

'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
Example:

Convert characters in the string to uppercase.
def upcase
  chars(Unicode.apply_mapping(@wrapped_string, :uppercase_mapping))
end