C0 code coverage information

Generated on Tue Oct 16 11:40:49 -0400 2007 with rcov 0.8.0


Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
Name Total lines Lines of code Total coverage Code coverage
lib/alexandria/smart_library.rb 497 403
49.5% 
43.4% 
  1 # Copyright (C) 2004-2006 Laurent Sansonetti
  2 #
  3 # Alexandria is free software; you can redistribute it and/or
  4 # modify it under the terms of the GNU General Public License as
  5 # published by the Free Software Foundation; either version 2 of the
  6 # License, or (at your option) any later version.
  7 #
  8 # Alexandria is distributed in the hope that it will be useful,
  9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 11 # General Public License for more details.
 12 #
 13 # You should have received a copy of the GNU General Public
 14 # License along with Alexandria; see the file COPYING.  If not,
 15 # write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 16 # Boston, MA 02111-1307, USA.
 17 
 18 module Alexandria
 19     class SmartLibrary < Array
 20         include GetText
 21         extend GetText
 22         bindtextdomain(Alexandria::TEXTDOMAIN, nil, nil, "UTF-8")
 23 
 24         ALL_RULES, ANY_RULE = 1, 2
 25         attr_reader :name
 26         attr_accessor :rules, :predicate_operator_rule
 27 
 28         DIR = File.join(ENV['HOME'], '.alexandria', '.smart_libraries')
 29         EXT = '.yaml'
 30 
 31         def initialize(name, rules, predicate_operator_rule)
 32             super()
 33             raise if name.nil? or rules.nil? or predicate_operator_rule.nil?
 34             @name = name
 35             @rules = rules
 36             @predicate_operator_rule = predicate_operator_rule
 37             save
 38             libraries = Libraries.instance
 39             libraries.add_observer(self)
 40             self.libraries = libraries.all_regular_libraries
 41             @cache = {}
 42         end
 43 
 44         def self.loadall
 45             a = []
 46             begin
 47                 # Deserialize smart libraries.
 48                 Dir.chdir(DIR) do
 49                     Dir["*" + EXT].each do |filename|
 50                         # Skip non-regular files.
 51                         next unless File.stat(filename).file?
 52                         
 53                         text = IO.read(filename)
 54                         hash = YAML.load(text)
 55                         begin
 56                             smart_library = self.from_hash(hash)
 57                             a << smart_library
 58                         rescue => e
 59                             puts "Cannot load serialized smart library: #{e}"
 60                             puts e.backtrace
 61                         end
 62                     end
 63                 end
 64 
 65             rescue Errno::ENOENT
 66                 # First run and no smart libraries yet? Provide some default 
 67                 # ones.
 68                 self.sample_smart_libraries.each do |smart_library|
 69                     smart_library.save
 70                     a << smart_library
 71                 end
 72              end
 73             a.each { |x| x.refilter }
 74             return a
 75         end
 76 
 77         def self.sample_smart_libraries
 78             a = []
 79 
 80             operands = Rule::Operands::LEFT
 81 
 82             # Favorite books.
 83             rule = Rule.new(operands.find { |x| x.book_selector == :rating },
 84                             Rule::Operators::IS,
 85                             UI::MainApp::MAX_RATING_STARS.to_s)
 86             a << self.new(_("Favorite"), [rule], ALL_RULES)
 87 
 88             # Loaned books.
 89             rule = Rule.new(operands.find { |x| x.book_selector == :loaned },
 90                             Rule::Operators::IS_TRUE,
 91                             nil)
 92             a << self.new(_("Loaned"), [rule], ALL_RULES)
 93             
 94             #Redd books.
 95             rule = Rule.new(operands.find { |x| x.book_selector == :redd },
 96                             Rule::Operators::IS_TRUE,
 97                             nil)
 98             a << self.new(_("Read"), [rule], ALL_RULES)
 99 
100             #Own books.
101             rule = Rule.new(operands.find { |x| x.book_selector == :own },
102                             Rule::Operators::IS_TRUE,
103                             nil)
104             a << self.new(_("Owned"), [rule], ALL_RULES)
105 
106             #Want books.
107             rule = Rule.new(operands.find { |x| x.book_selector == :want },
108                             Rule::Operators::IS_TRUE,
109                             nil)
110             rule2 = Rule.new(operands.find { |x| x.book_selector == :own },
111                             Rule::Operators::IS_NOT_TRUE,
112                             nil)
113             a << self.new(_("Wishlist"), [rule, rule2], ALL_RULES)
114 
115 
116             return a
117         end
118 
119         def self.from_hash(hash)
120             SmartLibrary.new(hash[:name],
121                              hash[:rules].map { |x| Rule.from_hash(x) },
122                              hash[:predicate_operator_rule] == :all \
123                                 ? ALL_RULES : ANY_RULE)
124         end
125 
126         def to_hash
127             {
128                 :name => @name,
129                 :predicate_operator_rule =>
130                     @predicate_operator_rule == ALL_RULES ? :all : :any,
131                 :rules => @rules.map { |x| x.to_hash }
132             }
133         end
134 
135         def name=(new_name)
136             if @name != new_name
137                 old_yaml = self.yaml
138                 @name = new_name
139                 FileUtils.mv(old_yaml, self.yaml)
140                 save
141             end
142         end
143 
144         def update(*params)
145             if params.first.is_a?(Libraries)
146                 libraries, action, library = params
147                 unless library.is_a?(self.class)
148                     self.libraries = libraries.all_libraries
149                     refilter
150                 end
151             elsif params.first.is_a?(Library)
152                 refilter
153             end
154         end
155 
156         def refilter
157             raise "need libraries" if @libraries.nil?
158             raise "no libraries" if @libraries.empty?
159             raise "need predicate operator" if @predicate_operator_rule.nil?
160             raise "need rule" if @rules.nil? or @rules.empty? 
161 
162             filters = @rules.map { |x| x.filter_proc }
163             selector = @predicate_operator_rule == ALL_RULES ? :all? : :any?
164 
165             self.clear
166             @cache.clear           
167  
168             @libraries.each do |library|
169                 filtered_library = library.select do |book|
170                     filters.send(selector) { |filter| filter.call(book) } #Problem here.
171                 end
172                 filtered_library.each { |x| @cache[x] = library }
173                 self.concat(filtered_library)
174             end
175             @n_rated = select { |x| !x.rating.nil? and x.rating > 0 }.length
176         end
177  
178         def cover(book)
179             @cache[book].cover(book)
180         end
181         
182         def yaml(book=nil)
183             if book
184                 @cache[book].yaml(book)
185             else
186                 File.join(DIR, @name + EXT)
187             end
188         end
189 
190         def save(book=nil)
191             if book
192                 @cache[book].save(book)
193             else
194                 FileUtils.mkdir_p(DIR)
195                 File.open(self.yaml, "w") { |io| io.puts self.to_hash.to_yaml }
196             end
197         end
198        
199         def save_cover(book, cover_uri)
200             @cache[book].save_cover(book)
201         end
202 
203         def cover(book)
204             @cache[book].cover(book)
205         end
206 
207         def final_cover(book)
208             @cache[book].final_cover(book)
209         end
210         
211         def copy_covers(somewhere)
212             FileUtils.rm_rf(somewhere) if File.exists?(somewhere)
213             FileUtils.mkdir(somewhere)
214             each do |book|
215                 library = @cache[book]
216                 next unless File.exists?(library.cover(book))
217                 FileUtils.cp(File.join(library.path, 
218                                        book.ident + Library::EXT[:cover]),
219                              File.join(somewhere, 
220                                        library.final_cover(book))) 
221             end
222         end
223 
224         def n_rated
225             @n_rated
226         end
227        
228         def n_unrated
229             length - n_rated
230         end
231         
232         def ==(object)
233             object.is_a?(self.class) && object.name == self.name
234         end
235 
236         @@deleted_libraries = []
237 
238         def self.deleted_libraries
239             @@deleted_libraries
240         end
241 
242         def self.really_delete_deleted_libraries
243             @@deleted_libraries.each do |library| 
244                 puts "Deleting smart library file (#{self.yaml})" if $DEBUG
245                 FileUtils.rm_rf(library.yaml)
246             end
247         end
248         
249         def delete
250             raise if @@deleted_libraries.include?(self)
251             @@deleted_libraries << self
252         end
253 
254         def deleted?
255             @@deleted_libraries.include?(self)
256         end
257 
258         def undelete
259             raise unless @@deleted_libraries.include?(self)
260             @@deleted_libraries.delete(self)
261         end
262         
263         #######
264         private
265         #######
266 
267         def libraries=(ary)
268             @libraries.each { |x| x.delete_observer(self) } if @libraries
269             @libraries = ary.select { |x| x.is_a?(Library) }
270             @libraries.each { |x| x.add_observer(self) } 
271         end
272 
273         ######
274         public
275         ######
276 
277         class Rule
278             include GetText
279             extend GetText
280             bindtextdomain(Alexandria::TEXTDOMAIN, nil, nil, "UTF-8")
281 
282             attr_accessor :operand, :operation, :value
283 
284             def initialize(operand, operation, value)
285                 raise if operand.nil? or operation.nil? # value can be nil
286                 @operand = operand
287                 @operation = operation
288                 @value = value 
289             end
290 
291             def self.from_hash(hash)
292                 operand = Operands::LEFT.find do |x|
293                     x.book_selector == hash[:operand]
294                 end
295                 operator = Operators::ALL.find do |x|
296                     x.sym == hash[:operation]
297                 end
298                 Rule.new(operand, operator, hash[:value])
299             end
300 
301             def to_hash
302                 {
303                     :operand => @operand.book_selector,
304                     :operation => @operation.sym,
305                     :value => @value
306                 }
307             end
308 
309             class Operand < Struct.new(:name, :klass)
310                 def <=>(x)
311                     self.name <=> x.name
312                 end
313             end
314 
315             class LeftOperand < Operand
316                 attr_accessor :book_selector
317                 
318                 def initialize(book_selector, *args)
319                     super(*args)
320                     @book_selector = book_selector
321                 end 
322             end
323 
324             class Operator < Struct.new(:sym, :name, :proc)
325                 def <=>(x)
326                     self.name <=> x.name
327                 end
328             end
329 
330             module Operands
331                 include GetText
332                 extend GetText
333                 bindtextdomain(Alexandria::TEXTDOMAIN, nil, nil, "UTF-8")
334 
335                 LEFT = [
336                     LeftOperand.new(:title, _("Title"), String),
337                     LeftOperand.new(:isbn, _("ISBN"), String),
338                     LeftOperand.new(:authors, _("Authors"), String),
339                     LeftOperand.new(:publisher, _("Publisher"), String),
340                     LeftOperand.new(:publish_year, _("Publish Year"), Integer),
341                     LeftOperand.new(:edition, _("Binding"), String),
342                     LeftOperand.new(:rating, _("Rating"), Integer),
343                     LeftOperand.new(:notes, _("Notes"), String),
344                     LeftOperand.new(:loaned, _("Loaning State"), TrueClass),
345                     LeftOperand.new(:loaned_since, _("Loaning Date"), Time),
346                     LeftOperand.new(:loaned_to, _("Loaning Person"), String),
347                     LeftOperand.new(:redd, _("Read"), TrueClass),
348                     LeftOperand.new(:own, _("Own"), TrueClass),
349                     LeftOperand.new(:want, _("Want"), TrueClass),
350                 ].sort
351 
352                 STRING = Operand.new(nil, String)
353                 INTEGER = Operand.new(nil, Integer)
354                 TIME = Operand.new(nil, Time)
355                 DAYS = Operand.new(_("days"), Integer)
356             end
357 
358             module Operators
359                 include GetText
360                 extend GetText
361                 bindtextdomain(Alexandria::TEXTDOMAIN, nil, nil, "UTF-8")
362 
363                 IS_TRUE = Operator.new(
364                     :is_true, 
365                     _("is set"), 
366                     proc { |x| x })
367                 IS_NOT_TRUE = Operator.new(
368                     :is_not_true, 
369                     _("is not set"), 
370                     proc { |x| !x })
371                 IS = Operator.new(
372                     :is, 
373                     _("is"), 
374                     proc { |x, y| x == y })
375                 IS_NOT = Operator.new(
376                     :is_not, 
377                     _("is not"), 
378                     proc { |x, y| x != y })
379                 CONTAINS = Operator.new(
380                     :contains, 
381                     _("contains"), 
382                     proc { |x, y| x.include?(y) })
383                 DOES_NOT_CONTAIN = Operator.new(
384                     :does_not_contain,
385                     _("does not contain"), 
386                     proc { |x, y| !x.include?(y) })
387                 STARTS_WITH = Operator.new(
388                     :starts_with, 
389                     _("starts with"),
390                     proc { |x, y| /^#{y}/.match(x) })
391                 ENDS_WITH = Operator.new(
392                     :ends_with, 
393                     _("ends with"),
394                     proc { |x, y| /#{y}$/.match(x) })
395                 IS_GREATER_THAN = Operator.new(
396                     :is_greater_than, 
397                     _("is greater than"),
398                     proc { |x, y| x > y })
399                 IS_LESS_THAN = Operator.new(
400                     :is_less_than,
401                     _("is less than"),
402                     proc { |x, y| x < y })
403                 IS_AFTER = Operator.new(
404                     :is_after,
405                     _("is after"), 
406                     IS_GREATER_THAN.proc)
407                 IS_BEFORE = Operator.new(
408                     :is_before,
409                     _("is before"), 
410                     IS_LESS_THAN.proc)
411                 IS_IN_LAST = Operator.new(
412                     :is_in_last_days,
413                     _("is in last"),
414                     proc { |x, y| Time.now - x <= 3600*24*y })
415                 IS_NOT_IN_LAST = Operator.new(
416                     :is_not_in_last_days,
417                     _("is not in last"),
418                     proc { |x, y| Time.now - x > 3600*24*y })
419 
420                 ALL = self.constants.map \
421                     { |x| self.module_eval(x) }.select \
422                     { |x| x.is_a?(Operator) }
423             end
424 
425             BOOLEAN_OPERATORS = [ 
426                 Operators::IS_TRUE,
427                 Operators::IS_NOT_TRUE
428             ].sort
429 
430             STRING_OPERATORS = [
431                 Operators::IS,
432                 Operators::IS_NOT,
433                 Operators::CONTAINS,
434                 Operators::DOES_NOT_CONTAIN,
435                 Operators::STARTS_WITH,
436                 Operators::ENDS_WITH
437             ].sort
438    
439             INTEGER_OPERATORS = [
440                 Operators::IS, 
441                 Operators::IS_NOT, 
442                 Operators::IS_GREATER_THAN, 
443                 Operators::IS_LESS_THAN
444             ].sort
445 
446             TIME_OPERATORS = [
447                 Operators::IS,
448                 Operators::IS_NOT,
449                 Operators::IS_AFTER, 
450                 Operators::IS_BEFORE, 
451                 Operators::IS_IN_LAST, 
452                 Operators::IS_NOT_IN_LAST
453             ].sort
454  
455             def self.operations_for_operand(operand)
456                 case operand.klass.name
457                     when 'String'
458                         STRING_OPERATORS.map { |x| [x, Operands::STRING] }
459                     when 'Integer'
460                         INTEGER_OPERATORS.map { |x| [x, Operands::INTEGER] }
461                     when 'TrueClass'
462                         BOOLEAN_OPERATORS.map { |x| [x, nil] }
463                     when 'Time'
464                         TIME_OPERATORS.map do |x|
465                             if x == Operators::IS_IN_LAST or
466                                x == Operators::IS_NOT_IN_LAST
467                                 
468                                 [x, Operands::DAYS]
469                             else
470                                 [x, Operands::TIME]
471                             end
472                         end
473                     else
474                         raise "invalid operand klass #{operand.klass}"
475                 end
476             end
477 
478             def filter_proc
479                 proc do |book|
480                 	begin 
481                     left_value = book.send(@operand.book_selector)
482                     rescue => e
483                     	puts e.message
484                     end
485                     right_value = @value
486                     if right_value.is_a?(String)
487                         left_value = left_value.to_s.downcase
488                         right_value = right_value.downcase
489                     end
490                     params = [left_value]
491                     params << right_value unless right_value.nil?
492                     @operation.proc.call(*params)
493                 end
494             end
495         end
496     end
497 end

Generated using the rcov code coverage analysis tool for Ruby version 0.8.0.

Valid XHTML 1.0! Valid CSS!