Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

Dustin Voss

« Newer Snippets
Older Snippets »
Showing 1-1 of 1 total  RSS 

Format strings like PRINT USING, for Ruby

This is an extension to Ruby's String class. Just put this code in a file and include it. This basically takes a string and adds literals and padding to it. For example, you can format a phone number like this:

   1  
   2  "5445556747".using('(###) ###-####', '', true)
   3     => (544) 555-6747


   1  
   2  require 'strscan'
   3  
   4  class String
   5  
   6    # Returns the string formatted according to a pattern.
   7    #
   8    # The pattern consists of placeholders and literals. The string is placed in
   9    # the placeholders, leaving the literals as they are. The result may be
  10    # truncated or padded if there are more placeholders than strings.
  11    #
  12    # Placeholders are '#' or '&'. Each '#' is replaced by one character from
  13    # the string, or the filler character if the string has no characters left.
  14    # The '&' is replaced by any remaining characters, or left out of the result
  15    # if there are no remaining characters. There can only be one '&' in the
  16    # pattern. If there is no '&', remaining characters are discarded.
  17    # 
  18    # '#' or '&' may be replaced by other characters if they are needed as
  19    # literals.
  20    #
  21    # Examples:
  22    # "123456789".using('###-##-####')
  23    #    => "123-45-6789"
  24    # "12345".using('###-##-####')
  25    #    => "123-45"
  26    # "12345".using('###-##-####', nil)
  27    #    => "12345"
  28    # "12345".using('###-##-####', ' ')
  29    #    => "123-45-    "
  30    # "5551212".using ('(###) ###-####', '', true)
  31    #    => "555-1212"
  32    # "873555121276668".using ('(###) ###-#### ext &', '', true)
  33    #    => "(873) 555-1212 ext 76668"
  34    # "KB5774X".using ('##-&-#')
  35    #    => "KB-5774-X"
  36    #
  37    # Parameters:
  38    # pattern -- The format string, see above.
  39    # fill    -- A string for padding. If the empty string, then the pattern is
  40    #            filled as much as possible, and the rest of the pattern is
  41    #            truncated. If nil, and the string does not fill the pattern,
  42    #            the string is returned unchanged.  Otherwise, the string is
  43    #            padded to fill the pattern, which is not truncated. Defaults to
  44    #            the empty string.
  45    # right   -- If true, the pattern is filled from right-to-left instead of
  46    #            from left-to-right, and truncated on the left instead of the
  47    #            right if needed. Default is false.
  48    # fixchar -- The single-character placeholder. Default is '#'.
  49    # remchar -- The remaining-character placeholder. Default is '&'.
  50    #
  51    def using(pattern, fill='', right=false, fixchar='#', remchar='&')
  52  
  53      remCount = pattern.count(remchar)
  54      raise ArgumentError.new("Too many #{remchar}") if remCount > 1
  55      raise ArgumentError.new("#{fixchar} too long") if fixchar.length > 1
  56      raise ArgumentError.new("#{remchar} too long") if remchar.length > 1
  57      raise ArgumentError.new("#{fill} too long")    if fill.length > 1
  58      remaining = remCount != 0
  59      slots = pattern.count(fixchar)
  60  
  61      # Return the string if it doesn't fit and we shouldn't even try,
  62      if fill.nil?
  63        return self if self.length < slots
  64        return self if self.length > slots and !remaining
  65      end
  66  
  67      # Pad and clone the string if necessary.
  68      source =  if fill.nil? || fill.empty? then
  69                  self
  70                elsif right then
  71                  self.rjust(slots, fill)
  72                else
  73                  self.ljust(slots, fill)
  74                end
  75      
  76      # Truncate the string if necessary.
  77      if source.length > slots && !remaining then
  78        source = right ? source[-source.length, source.length] :
  79                         source[0, source.length]
  80      end
  81  
  82      # Truncate pattern if needed.
  83      if !fill.nil? && fill.empty? then
  84        
  85        if source.length < slots  # implies '&' can be ignored
  86          keepCount = source.length # Number of placeholders we are keeping
  87          leftmost, rightmost = 0, pattern.length - 1
  88          if right then
  89            # Look right-to-left until we find the last '#' to keep.
  90            # Loop starts at 1 because 0th placeholder is in the inject param.
  91            leftmost = (1...keepCount).inject(pattern.rindex(fixchar)) {
  92              |leftmost, n| pattern.rindex(fixchar, leftmost - 1) }
  93          else
  94            # Look left-to-right until we find the last '#' to keep.
  95            rightmost = (1...keepCount).inject(pattern.index(fixchar)) {
  96              |rightmost, n| pattern.index(fixchar, rightmost + 1) }
  97          end
  98          pattern = pattern[leftmost..rightmost]
  99          slots = pattern.count(fixchar)
 100        end
 101      
 102        # Trim empty '&' up to nearest placeholder. If a '&' goes empty, the
 103        # literals between it and the nearest '#' are probably also unnecessary.
 104        if source.length == slots then
 105          if pattern.match("^#{Regexp.escape(remchar)}") then
 106            pattern = pattern[pattern.index(fixchar) || 0 ... pattern.length]
 107          elsif pattern.match("#{Regexp.escape(remchar)}$") then
 108            pattern = pattern[0 ... (pattern.rindex(fixchar) + fixchar.length) || pattern.length]
 109          end
 110        end
 111  
 112      end
 113        
 114      # Figure out how long the remainder will be when we get to it.
 115      remSize = source.length - slots
 116      if remSize < 0 then remSize = 0; end
 117      
 118      # Make the result.
 119      scanner = ::StringScanner.new(pattern)
 120      sourceIndex = 0
 121      result = ''
 122      fixRegexp = Regexp.new(Regexp.escape(fixchar))
 123      remRegexp = Regexp.new(Regexp.escape(remchar))
 124      while not scanner.eos?
 125        if scanner.scan(fixRegexp) then
 126          result += source[sourceIndex].chr
 127          sourceIndex += 1
 128        elsif scanner.scan(remRegexp) then
 129          result += source[sourceIndex, remSize]
 130          sourceIndex += remSize
 131        else
 132          result += scanner.getch
 133        end
 134      end
 135      
 136      result
 137    end
 138  end
« Newer Snippets
Older Snippets »
Showing 1-1 of 1 total  RSS