Posted By

Agent on 07/28/07


Tagged

format ruby String text formatting


Versions (?)

Who likes this?

1 person have marked this snippet as a favorite

webstic


Format strings like PRINT USING


 / Published in: 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 with optional area code like this:

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

Report this snippet  

You need to login to post a comment.