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