Unicode-compliant string lengths
[rat.git] / lib / rat / core_ext / string.rb
blob03b8b7e1c04b020e5fe3d10766034f9e73fb0525
1 require 'stringray'
3 class String
4   include StringRay
5   
6   # Constantizes a string, changing it from snake_case or 'space case' to
7   # TitleCase, however necessary
8   def constantize
9     str = self
10     str = str.gsub /\s/, '_'
11     str = str.gsub(/(^|_)(\w)/) { "#{$2.capitalize}" }
12   end
13   
14   def /(o)
15     File.join(self, o.to_s)
16   end
17   
18   def indent spaces
19     if spaces.respond_to? :to_s # duck,
20       self.split("\n").map {|s| [spaces, s].join }.join("\n")
21     elsif spaces.respond_to? :to_i # duck,
22       self.split("\n").map {|s| [(' ' * spaces), s].join }.join("\n")
23     else # goose!
24       raise ArgumentError, "#{spaces} is neither string-ish nor numeric-ish"
25     end
26   end
27   
28   # Ruby 1.8's #length doesn't like multibyte Unicode. Thanks Mikael Høilund!
29   def length
30     self.scan(/./um).size
31   end
32   
33   # Simply returns an array of two string pieces split at +length+.
34   def split_at length
35     self.scan /.{1,#{length}}/
36   end
37   
38   # Wraps a string, *intelligently*
39   def wrap width, min = nil
40     raise ArgumentError, "#{width} is not numeric-ish" unless width.respond_to? :to_i
41     min ||= (width.to_i * 0.75).to_i # Default to about a third of the full width
42     raise ArgumentError, "#{min} is not numeric-ish" unless min.respond_to? :to_i
43     
44     
45     self.inject([""]) do |wrapped, word|
46       #puts "word: #{word.inspect}, current line: #{wrapped.last.inspect}"
47       # If we're still short enough to fit the word, do so
48       if wrapped.last.length + word.rstrip.length <= width
49         #puts "- new length #{wrapped.last.length + word.rstrip.length} (#{wrapped.last.length} + #{word.rstrip.length}) is less than #{width}\n\n"
50         wrapped.last << word
51       # Else, if we're less than minimum width
52       elsif wrapped.last.length < min
53         #puts "- new length #{wrapped.last.length + word.rstrip.length} (#{wrapped.last.length} + #{word.rstrip.length}) would be more than #{width}"
54         #puts "- current length #{wrapped.last.length} is less than #{min}\n\n"
55         bits = word.split_at(width - wrapped.last.length)
56         wrapped.last << bits.shift
57         bits.join.split_at(width)
58         bits.each {|bit| wrapped << bit}
59       # Else if neither can fit on current line, nor is line short enough; and
60       # the word is short enough to fit on the new line
61       elsif word.chomp.length < width
62         #puts "- new length #{wrapped.last.length + word.rstrip.length} (#{wrapped.last.length} + #{word.rstrip.length}) would be more than #{width}"
63         #puts "- current length #{wrapped.last.length} is more than #{min}"
64         #puts "- word's length #{word.chomp.length} is less than #{width}\n\n"
65         wrapped << word
66       # If it can't fit on the current line, and it can't fit wholly on a line
67       # by it's own
68       else
69         #puts "- new length #{wrapped.last.length + word.rstrip.length} (#{wrapped.last.length} + #{word.rstrip.length}) would be more than #{width}"
70         #puts "- current length #{wrapped.last.length} is more than #{min}"
71         #puts "- word's length #{word.chomp.length} is more than #{width}"
72         bits = word.split_at(width)
73         bits.each {|bit| wrapped << bit}
74       end
75       
76       wrapped
77     end.join("\n")
78   end
79   
80 end