require 'enumerator' require 'amazon/aws' require 'amazon/aws/search' module Cocoron module BookID class InvalidBookIDError < ArgumentError; end def self.parse(str) id_str = str.delete('-').upcase cs = GenericID.descendants.select{|c| c.template.match(id_str) } if cs.empty? or (id_str.length == 13 and BookID.invalid_jan?(id_str)) then raise InvalidBookIDError, "Invalid Book ID: #{str}" elsif cs.length > 1 then raise ScriptError, "Ambigous definitions of Book ID format! " + "Check the Implementations: " + cs.collect{|c| c.name }.join(', ') else cs.first.new(id_str) end end def self.invalid_jan?(str) jan_csum(str) != str[-1, 1] end def self.jan_csum(str) n = (0...12).inject(0){|s, i| s + ((i % 2 == 0) ? 1 : 3) * str[i, 1].to_i } ((10 - (n % 10)) % 10).to_s end end end module Cocoron::BookID class GenericID include Comparable def initialize(str) raise NotImplementedError, 'Abstract class' end @@descendants = Array.new def self.inherited(c) c.instance_eval { def inherited(sc) superclass.inherited(sc) end } @@descendants.push(c) end def self.descendants @@descendants end def <=>(other) other.is_a?(GenericID) ? [self.code.length, self.code] <=> [other.code.length, other.code] : nil end def create_book _create_book({}) end def to_s "#{self.sort}: #{self.code}" end def code @code end def hash @code.hash end def eql?(other) other.is_a?(self.class) && self.code == other.code end alias :== :eql? def inspect "#<#{self.class.name}:#{@code}>" end def sort self.class.name.split('::').last end private def _create_book(book_info) ::Cocoron::Book.new(self, book_info) end def invalid_id_error(s) raise InvalidBookIDError, "Invalid #{self.sort}: #{s}" end end end module Cocoron::BookID class CocoronID < GenericID def initialize(str) @code = str end def self.template /^[a-z]\d{8}$/o end end end module Cocoron::BookID class ISBN < GenericID @@locale = 'jp' @@search_cache_p = true def initialize(str) @code = validate_isbn(str) end def self.template /^(?: \d{9}(?:\d|X) # ISBN-10 |97[89]\d{10} # ISBN-13(JAN, EAN) )$/ox end def create_book _create_book(asin_search) end def self.amazon_locale=(l) @@locale = l end def self.amazon_search_do_cache @@search_cache_p = true end def self.amazon_search_do_not_cache @@search_cache_p = false end def self.isbn10to13(str, prefix = '978') if not isbn10?(str) then raise ArgumentError, "ISBN-10 required, but got: #{str}" else s = prefix + str[0..-2] s + isbn_csum13(s) end end def self.isbn13to10(str) if not isbn13?(str) then raise ArgumentError, "ISBN-13 required, but got: #{str}" else c = str[3, 9] c + isbn_csum10(c) end end def self.isbn10?(str) str.length == 10 && isbn_csum10(str) == str[-1, 1] end def self.isbn13?(str) str.length == 13 && isbn_csum13(str) == str[-1, 1] end def self.isbn_csum10(str) sum = (1..9).inject(0){|sum, i| sum + str[i - 1, 1].to_i * i } % 11 %w(0 1 2 3 4 5 6 7 8 9 X)[sum] end def self.isbn_csum13(str) ::Cocoron::BookID.jan_csum(str) end private def validate_isbn(str) if ::Cocoron::BookID::ISBN.isbn10?(str) then ::Cocoron::BookID::ISBN.isbn10to13(str) elsif ::Cocoron::BookID::ISBN.isbn13?(str) then str else invalid_id_error(str) end end def asin_search code = ::Cocoron::BookID::ISBN.isbn13to10(@code) il = Amazon::AWS::ItemLookup.new('ASIN', 'ItemId' => code) req = Amazon::AWS::Search::Request.new(nil, nil, @@locale, @@search_cache_p) res = req.search(il) ps = res.item_lookup_response.items raise ArgumentError, 'Too many results' if ps.length > 1 p = ps.first.item.item_attributes {:title => p.title.to_s, :authors => Array((p.author.collect{|a| a.to_s } rescue [])), :publisher => p.publisher.to_s, :edition => p.first[:binding].to_s, :price => p.list_price.formatted_price.to_s, :release_date => p.publication_date.to_s, :image_url_small => (ps.first.item.small_image.url.to_s rescue ""), :image_url_medium => (ps.first.item.medium_image.url.to_s rescue ""), :image_url_large => (ps.first.item.large_image.url.to_s rescue "")} rescue Amazon::AWS::Error::InvalidParameterValue {} end end end module Cocoron::BookID class ISSN < GenericID def initialize(str) @code = str end def self.template /^(?: \d{8} # Raw ISSN |977\d{10} # JAN )$/ox end def normalize_issn(str) if str.length == 8 then if valid_issn?(str) then str else invalid_id_error(str) end else str[3, 7] + issn_csum(str) end end def valid_issn?(str) str.length == 8 && (str[-1, 1] == issn_csum(str)) end def issn_csum(str) c = (8..2).inject(0){|s, i| i * str[8 - i, 1].to_i } % 11 %w(0 1 2 3 4 5 6 7 8 9 X)[c] end end end module Cocoron::BookID class MagazineCode < GenericID def initialize(str) @code = normalize_mcode(str) end def self.template /^(?: \d{7} # Raw Magazine Code |491\d{10} # New JAN |1[01]\d{11} # Old JAN )$/xo end def normalize_mcode(str) case str.length when 7 then str when 13 then if /^491/ =~ str then str[4, 7] else str[2, 7] end end end end end