Pushing a Ruby warning when overriding String#each, but only worrying about it pre-1.9
[stringray.git] / lib / stringray.rb
blobf599a8f79093f5f82dc78862b9b6aae4d8bd12f1
1 require 'stringray/core_ext'
3 module StringRay
4   VERSION = 2
5   
6   @@whitespace = nil
7   @@delemiters = nil
8   
9   ##
10   # @see #enumerate
11   # Controls how +#enumerate+ deals with whitespace.
12   # 
13   # @param [Symbol] whitespace How to handle whitespace - :attach_before,
14   #   :standalone, or :attach_after
15   def self.whitespace= whitespace
16     @@whitespace = whitespace
17   end
18   
19   def self.whitespace
20     @@whitespace ||= :attach_before
21   end
23   ##
24   # @see #enumerate
25   # Controls how +#enumerate+ deals with delemiters.
26   # 
27   # @param [Symbol] delemiters How to handle delemiters - :attach_before,
28   #   :standalone, or :attach_after
29   def self.delemiters= delemiters
30     @@delemiters = delemiters
31   end
32   
33   def self.delemiters
34     @@delemiters ||= :attach_before
35   end
36   
37   ##
38   # Splits a string into an array of +StringRay+ container objects (+Word+,
39   # +Whitespace+, and +Delimiter+).
40   # 
41   # @yield [element] Allows each 'element' of the string to be operated on
42   #   after it is processed
43   # @yieldparam [Word, Whitespace, Delimiter] element The last processed
44   #   string 'element'
45   # @return [Array[Word, Whitespace, Delimiter]] An array of +StringRay+
46   #   container objects
47   # @since 2
48   def to_stray
49     ray = []
50     new_element = lambda do |element|
51       yield ray.last if block_given? unless ray.empty?
52       ray << element
53     end
54     
55     self.scan(/./um) do |char|
56       if Delimiter::Characters.include? char
57         new_element[Delimiter.new(char)]
58         
59       elsif Whitespace::Characters.include? char
60         if ray.last.is_a? Whitespace
61           ray.last << char
62         else
63           new_element[Whitespace.new(char)]
64         end
65         
66       else
67         if ray.last.is_a? Word
68           ray.last << char
69         else
70           new_element[Word.new(char)]
71         end
72         
73       end
74     end
75     
76     if block_given?
77       yield ray.last
78       self
79     else
80       ray
81     end
82   end
83   
84   ##
85   # @see #to_stray
86   # @see #whitespace
87   # @see #delemiters
88   # Enumerates a string, similar to +#to_stray+, but returning an array of
89   # plain +String+s instead of container objects.
90   # 
91   # @param [Hash] options A hash of options
92   # @yield [word] Allows each word in the string to be operated on after it is
93   #   processed
94   # @yieldparam [String] word The last processed word
95   # @return [Array[String]] An array of words
96   # @since 1
97   def enumerate options = {}, &block
98     mapped = []
99     attach_before_next = []
100     
101     self.to_stray do |element|
102       case element
103       when Delimiter
104         case options[:delemiters] || StringRay::delemiters
105         when :standalone
106           mapped << [element]
107         when :attach_after
108           attach_before_next << element
109         else
110           if attach_before_next.empty?
111             if mapped.last
112               mapped.last << element
113             else
114               attach_before_next << element
115             end
116           else
117             attach_before_next << element
118           end
119         end
120         
121       when Whitespace
122         case options[:whitespace] || StringRay::whitespace
123         when :standalone
124           mapped << [element]
125         when :attach_after
126           attach_before_next << element
127         else
128           if attach_before_next.empty?
129             if mapped.last
130               mapped.last << element
131             else
132               attach_before_next << element
133             end
134           else
135             attach_before_next << element
136           end
137         end
138         
139       when Word
140         if not attach_before_next.empty?
141           mapped << [attach_before_next, element].flatten
142           attach_before_next = []
143         else
144           mapped << [element]
145         end
146         
147       end
148     end
149     
150     if not attach_before_next.empty?
151       mapped << [Word.new] unless mapped.last
152       (mapped.last << attach_before_next).flatten!
153     end
154     
155     mapped.map do |arr|
156       string = arr.map{|w|w.to_s}.join
157       yield string if block_given?
158       string
159     end
160   end
161   
162   # @deprecated
163   alias_method :each_word, :enumerate
164   
165   # @see StringRay::Word.new
166   def Word word; Word.new word; end
167   
168   ##
169   # A wrapper class for strings that are 'words' in and of themselves,
170   # composed of 'word characters'.
171   class Word < String
172     def inspect
173       "(#{self})"
174     end
175   end
176   
177   # @see StringRay::Whitespace.new
178   def Whitespace whitespace; Whitespace.new whitespace; end
179   
180   ##
181   # A wrapper class for strings that are 'whitespace' composed of 'whitespace
182   # characters'.
183   class Whitespace < String
184     Characters = [" ", "\t", "\n"]
185     
186     def inspect
187       "#{self}"
188     end
189   end
190   
191   # @see StringRay::Delimiter.new
192   def Delimiter delimiter; Delimiter.new delimiter; end
193   
194   ##
195   # A wrapper class for strings that are 'delimiters' composed of 'delimiter
196   # characters'.
197   class Delimiter < String
198     Characters = ['-', ',', '.', '?', '!', ':', ';', '/', '\\', '|']
199     
200     def inspect
201       "<#{self}>"
202     end
203   end
204   
205   # This is mixed into any class including +StringRay+. It exposes
206   # +::make_enumerable!+ to said class.
207   module Extendables
208     
209     ##
210     # This overrides +String#each+ with +StringRay#enumerate+, thus allowing
211     # us to include +Enumerable+. Be careful, this breaks lots of existing
212     # code which depends on the old methodology of +String#each+! The
213     # overridden +String#each+ functionality will be exposed as
214     # +String#each_at+.
215     def make_enumerable!
216       self.class_eval do
217         if RUBY_VERSION <= "1.9"
218           Kernel::warn "overriding String#each with StringRay#enumerate; this may break old libaries!"
219           alias_method :each_at, :each
220         end
221         alias_method :each, :enumerate
222         
223         include Enumerable
224       end
225     end
226     
227   end
228   
229   def self.included klass
230     klass.send :extend, Extendables
231   end