bookland 1.0.2 → 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -4,5 +4,4 @@ rvm:
4
4
  - 1.9.3
5
5
  - jruby
6
6
  - rbx
7
- - rbx-2.0
8
7
  - ree
data/Gemfile CHANGED
@@ -1,5 +1,2 @@
1
1
  source 'http://rubygems.org'
2
-
3
2
  gemspec
4
-
5
- gem 'rake'
data/LICENSE CHANGED
@@ -1,28 +1,22 @@
1
- Copyright (c) 2008, Keiichirou SHIKANO <k16.shikano@gmail.com>
2
- All rights reserved.
1
+ (The MIT License)
3
2
 
4
- Redistribution and use in source and binary forms, with or without
5
- modification, are permitted provided that the following conditions
6
- are met:
3
+ Copyright (c) 2012 Hakan Ensari
7
4
 
8
- * Redistributions of source code must retain the above copyright
9
- notice,this list of conditions and the following disclaimer.
10
- * Redistributions in binary form must reproduce the above
11
- copyright notice, this list of conditions and the following
12
- disclaimer in the documentation and/or other materials provided
13
- with the distribution.
14
- * Neither the name of the Keiichirou SHIKANO nor the names of its
15
- contributors may be used to endorse or promote products derived
16
- from this software without specific prior written permission.
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
17
12
 
18
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24
- TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
25
- OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26
- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,28 +1,34 @@
1
1
  # Bookland
2
2
 
3
- [Bookland] [1] provides an ISBN class in Ruby.
3
+ [![travis] [1]] [2]
4
4
 
5
- [![travis](https://secure.travis-ci.org/hakanensari/bookland.png)](http://travis-ci.org/hakanensari/bookland)
5
+ [Bookland] [3] provides ISBN and EAN classes in Ruby.
6
6
 
7
- ## Usage
7
+ ## Installation
8
8
 
9
- require "bookland"
9
+ ```ruby
10
+ # Gemfile
11
+ gem 'bookland', '~> 2.0.0.beta'
12
+ ```
10
13
 
11
- isbn = ISBN.new("0262011530")
12
- isbn.to_isbn13
13
- => "9780262011532"
14
- isbn.valid?
15
- => true
14
+ ## Usage
16
15
 
17
- Bookland provides certain utility methods defined on the class level:
16
+ ```ruby
17
+ include 'bookland'
18
18
 
19
- ISBN.to_13("0262011530")
20
- => "9780262011532"
19
+ isbn = ISBN.new "9780262011532"
20
+ isbn.valid? # => true
21
+ isbn10 = isbn.to_isbn_10
22
+ isbn10.to_s # => "0262011530"
23
+ ```
21
24
 
22
- ISBN.to_10("9780262011532")
23
- => "0262011530"
25
+ Alternatively, use utility methods defined on the class level:
24
26
 
25
- ISBN.valid?("9780262011532")
26
- => true
27
+ ```ruby
28
+ EAN.valid? '0814916013890' # => true
29
+ ISBN.valid? '0814916013890' # => false
30
+ ```
27
31
 
28
- [1]: http://en.wikipedia.org/wiki/Bookland
32
+ [1]: https://secure.travis-ci.org/hakanensari/bookland.png
33
+ [2]: http://travis-ci.org/hakanensari/bookland
34
+ [3]: http://en.wikipedia.org/wiki/Bookland
data/Rakefile CHANGED
@@ -1,11 +1,9 @@
1
- require 'bundler'
2
- require 'rspec/core/rake_task'
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
3
 
4
- Bundler::GemHelper.install_tasks
5
-
6
- desc 'Run all specs in spec directory'
7
- RSpec::Core::RakeTask.new(:spec) do |spec|
8
- spec.pattern = 'spec/**/*_spec.rb'
4
+ Rake::TestTask.new do |test|
5
+ test.libs += %w{lib test}
6
+ test.test_files = FileList['test/**/*_test.rb']
9
7
  end
10
8
 
11
- task :default => :spec
9
+ task :default => :test
data/bookland.gemspec CHANGED
@@ -7,17 +7,15 @@ Gem::Specification.new do |s|
7
7
  s.version = Bookland::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ['Hakan Ensari']
10
- s.email = 'code@papercavalier.com'
10
+ s.email = 'hakan.ensari@papercavalier.com'
11
11
  s.homepage = 'https://github.com/hakanensari/bookland'
12
- s.summary = %q{An ISBN class}
13
- s.description = %q{Bookland provides an ISBN class.}
12
+ s.summary = %q{EAN and ISBN classes}
13
+ s.description = %q{Bookland provides EAN and ISBN classes.}
14
14
 
15
- s.rubyforge_project = 'bookland'
15
+ s.add_development_dependency 'rake', '~> 0.9.2'
16
16
 
17
17
  s.files = `git ls-files`.split("\n")
18
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ['lib']
21
-
22
- s.add_development_dependency('rspec', '~> 2.6')
23
21
  end
data/lib/bookland.rb CHANGED
@@ -1,5 +1,6 @@
1
- require 'bookland/isbn'
1
+ require 'bookland/identifier'
2
+ require 'bookland/invalid_isbn'
2
3
 
3
- # Include Bookland in global namespace.
4
- #
5
- Object.send(:include, Bookland)
4
+ require 'bookland/ean'
5
+ require 'bookland/isbn'
6
+ require 'bookland/isbn_10'
@@ -0,0 +1,17 @@
1
+ module Bookland
2
+ # An International Article Number.
3
+ class EAN < Identifier
4
+ # Calculates the checksum for the 12-digit payload of an EAN.
5
+ def self.calculate_checksum(payload)
6
+ payload.map! &:to_i
7
+ weights = [1, 3] * 6
8
+ sum = payload.zip(weights).inject(0) { |a, (i, j)| a + i * j }
9
+
10
+ ((10 - sum % 10) % 10).to_s
11
+ end
12
+
13
+ def valid?
14
+ @raw.size == 13 && super
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ module Bookland
2
+ # An abstract unique commercial identifier for a product.
3
+ class Identifier
4
+ def self.calculate_checksum(payload)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def self.valid?(raw)
9
+ new(raw).valid?
10
+ end
11
+
12
+ def initialize(raw)
13
+ @raw = raw
14
+ end
15
+
16
+ def checksum
17
+ digits[-1]
18
+ end
19
+
20
+ def payload
21
+ digits[0...-1]
22
+ end
23
+
24
+ def to_s
25
+ @raw
26
+ end
27
+
28
+ def valid?
29
+ checksum == self.class.calculate_checksum(payload)
30
+ end
31
+
32
+ def ==(other)
33
+ to_s == other.to_s
34
+ end
35
+
36
+ private
37
+
38
+ def digits
39
+ @digits ||= @raw.split ''
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ module Bookland
2
+ class InvalidISBN < StandardError
3
+ end
4
+ end
data/lib/bookland/isbn.rb CHANGED
@@ -1,139 +1,24 @@
1
- # [Bookland][bl] is an ISBN class in Ruby.
2
- #
3
- # [bl]: http://en.wikipedia.org/wiki/Bookland
4
1
  module Bookland
5
- class ISBN
6
- class << self
7
- # Converts specified string to a 10-digit ISBN and returns its
8
- # string representation.
9
- def to_10(isbn)
10
- new(isbn).to_isbn10.to_s
11
- end
12
-
13
- # Converts specified string to a 13-digit ISBN and returns its
14
- # string representation.
15
- def to_13(isbn)
16
- new(isbn).to_isbn13.to_s
17
- end
18
-
19
- # Returns `true` if the specified string is a valid ISBN.
20
- def valid?(isbn)
21
- new(isbn).valid?
22
- end
23
- end
24
-
25
- # Instantiates a new ISBN object.
26
- #
27
- # Takes an optional string. The string should look like an ISBN and
28
- # may contain extra characters that are traditionally used to
29
- # format ISBNs.
30
- #
31
- # The following are valid instantiations for the same ISBN:
32
- #
33
- # isbn13 = ISBN.new('9780826476951')
34
- # isbn10 = ISBN.new('0-8264-7695-3')
35
- #
36
- def initialize(seed = nil)
37
- self.seed = seed
38
- end
39
-
40
- # Returns +true+ if the ISBN is equal to another specified ISBN.
41
- def ==(other)
42
- to_isbn13.to_s == other.to_isbn13.to_s
43
- end
44
-
45
- # Inspecting an ISBN object will return its string representation.
46
- def inspect
47
- to_s
48
- end
49
-
50
- # Sets the seed of the ISBN based on a specified string.
51
- def seed=(seed)
52
- @raw = seed.gsub(/[^\dx]/i, '').split('') rescue []
2
+ # An EAN that identifies a book.
3
+ class ISBN < EAN
4
+ # Converts the given ISBN to an ISBN-10.
5
+ def self.to_isbn_10(raw)
6
+ new(raw).to_isbn_10
53
7
  end
54
8
 
55
- # Casts the ISBN to a 10-digit ISBN.
56
- def to_isbn10
57
- raise ISBNError unless valid?
58
-
59
- if isbn13?
60
- raw = @raw[3..11]
61
- ISBN.new((raw << check_digit_10(raw)).to_s)
62
- else
63
- dup
64
- end
65
- end
66
-
67
- def to_isbn13
68
- raise ISBNError unless valid?
69
-
70
- if isbn10?
71
- raw = @raw[0..8].unshift('9', '7', '8')
72
- ISBN.new((raw << check_digit_13(raw)).to_s)
73
- else
74
- dup
75
- end
76
- end
9
+ # Converts the ISBN to an ISBN-10.
10
+ def to_isbn_10
11
+ raise InvalidISBN unless valid?
77
12
 
78
- # Returns a string representation of the ISBN.
79
- #
80
- # Takes an optional list of integers, which it then uses to dashify
81
- # the ISBN like so:
82
- #
83
- # isbn = ISBN.new('0826476953')
84
- # isbn.to_s(1,4,4,1)
85
- # => "0-8264-7695-3"
86
- #
87
- def to_s(*blocks)
88
- return nil unless valid?
13
+ new_payload = payload[3..11]
14
+ new_checksum = ISBN10.calculate_checksum new_payload
89
15
 
90
- raw = @raw.dup
91
- if blocks.any?
92
- (blocks.map { |i| raw.shift(i).join } << raw.join).delete_if(&:empty?).join('-')
93
- else
94
- raw.join
95
- end
16
+ (new_payload << new_checksum).join
96
17
  end
97
18
 
98
- # Returns +true+ if the ISBN is valid.
19
+ # Whether the ISBN is valid.
99
20
  def valid?
100
- if isbn10?
101
- @raw[9] == check_digit_10(@raw)
102
- elsif isbn13?
103
- @raw[12] == check_digit_13(@raw)
104
- else
105
- false
106
- end
107
- end
108
-
109
- private
110
-
111
- def check_digit_10(raw)
112
- cd = 11 - 10.downto(2).to_a.zip(raw[0..8].map(&:to_i)).map { |n,m| n * m }.inject(0) { |i,j| i + j } % 11
113
-
114
- case cd
115
- when 0..9 then cd.to_s
116
- when 10 then 'X'
117
- when 11 then '0'
118
- end
119
- end
120
-
121
- def check_digit_13(raw)
122
- ((10 - ([1, 3] * 6).zip(raw[0..11].map(&:to_i)).map { |n,m| n * m }.inject(0) { |i,j| i + j } % 10) % 10).to_s
123
- end
124
-
125
- def isbn10?
126
- @raw.length == 10
127
- end
128
-
129
- def isbn13?
130
- @raw.length == 13
131
- end
132
- end
133
-
134
- class ISBNError < StandardError
135
- def initialize(msg='Invalid ISBN')
136
- super
21
+ @raw.match(/^97[89]/) && super
137
22
  end
138
23
  end
139
24
  end
@@ -0,0 +1,37 @@
1
+ module Bookland
2
+ # The now-obsolete 10-digit ISBN.
3
+ class ISBN10 < Identifier
4
+ # Calculates the checksum for the 9-digit payload of an ISBN-10.
5
+ def self.calculate_checksum(payload)
6
+ payload.map! &:to_i
7
+ weights = 10.downto(2).to_a
8
+ sum = payload.zip(weights).inject(0) { |a , (i, j)| a + i * j }
9
+ checksum = 11 - sum % 11
10
+
11
+ case checksum
12
+ when 0..9 then checksum.to_s
13
+ when 10 then 'X'
14
+ when 11 then '0'
15
+ end
16
+ end
17
+
18
+ # Converts the given ISBN-10 to an ISBN.
19
+ def self.to_isbn(raw)
20
+ new(raw).to_isbn
21
+ end
22
+
23
+ # Converts the ISBN-10 to an ISBN.
24
+ def to_isbn
25
+ raise InvalidISBN unless valid?
26
+
27
+ new_payload = [9, 7, 8] + payload
28
+ new_checksum = ISBN.calculate_checksum new_payload
29
+
30
+ (new_payload << new_checksum).join
31
+ end
32
+
33
+ def valid?
34
+ @raw.size == 10 && super
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Bookland
2
- VERSION = '1.0.2'
2
+ VERSION = '2.0.0.beta'
3
3
  end
@@ -0,0 +1,95 @@
1
+ $:.push File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'test/unit'
4
+ require 'bookland'
5
+
6
+ include Bookland
7
+
8
+ class TestIdentifier < Test::Unit::TestCase
9
+ def setup
10
+ @id = Identifier.new '123'
11
+ end
12
+
13
+ def test_checksum
14
+ assert_equal '3', @id.checksum
15
+ end
16
+
17
+ def test_payload
18
+ assert_equal ['1', '2'], @id.payload
19
+ end
20
+
21
+ def test_comparison
22
+ assert Identifier.new('1') == Identifier.new('1')
23
+ assert Identifier.new('1') != Identifier.new('2')
24
+ end
25
+ end
26
+
27
+ class TestEAN < Test::Unit::TestCase
28
+ def test_validates_an_ean
29
+ assert EAN.valid? '0814916013890'
30
+ end
31
+
32
+ def test_does_not_validate_if_not_13_digits
33
+ assert !EAN.valid?('978082647694')
34
+ assert !EAN.valid?('97808264769444')
35
+ end
36
+
37
+ def test_does_not_validate_if_checksum_not_correct
38
+ assert !EAN.valid?('9780826476940')
39
+ end
40
+ end
41
+
42
+ class TestISBN < Test::Unit::TestCase
43
+ def test_validates_isbns
44
+ File.open(File.expand_path('../isbns', __FILE__)).each do |line|
45
+ isbn = line.split.last.chomp
46
+ assert ISBN.valid? isbn
47
+ end
48
+ end
49
+
50
+ def test_does_not_validate_an_ean_that_is_not_a_book
51
+ assert !ISBN.valid?('0814916013890')
52
+ end
53
+
54
+ def test_converts_to_isbn_10
55
+ isbn = ISBN.new '9780143105824'
56
+ assert_equal '0143105825', isbn.to_isbn_10
57
+ end
58
+
59
+ def test_raises_error_when_converting_invalid_isbn
60
+ isbn = ISBN.new '9780143105820'
61
+ assert_raise InvalidISBN do
62
+ isbn.to_isbn_10
63
+ end
64
+ end
65
+ end
66
+
67
+ class TestISBN10 < Test::Unit::TestCase
68
+ def test_validates_isbn_10s
69
+ File.open(File.expand_path('../isbns', __FILE__)).each do |line|
70
+ isbn10 = line.split.first.chomp
71
+ assert ISBN10.valid? isbn10
72
+ end
73
+ end
74
+
75
+ def test_converts_to_isbn
76
+ isbn10 = ISBN10.new '0143105825'
77
+ assert_equal '9780143105824', isbn10.to_isbn
78
+ end
79
+
80
+ def test_raises_error_when_converting_invalid_isbn10
81
+ isbn10 = ISBN10.new '0143105820'
82
+ assert_raise InvalidISBN do
83
+ isbn10.to_isbn
84
+ end
85
+ end
86
+
87
+ def test_does_not_validate_if_not_13_digits
88
+ assert !EAN.valid?('014310582')
89
+ assert !EAN.valid?('01431058255')
90
+ end
91
+
92
+ def test_does_not_validate_if_checksum_not_correct
93
+ assert !EAN.valid?('0143105820')
94
+ end
95
+ end