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 +0 -1
- data/Gemfile +0 -3
- data/LICENSE +19 -25
- data/README.md +23 -17
- data/Rakefile +6 -8
- data/bookland.gemspec +4 -6
- data/lib/bookland.rb +5 -4
- data/lib/bookland/ean.rb +17 -0
- data/lib/bookland/identifier.rb +42 -0
- data/lib/bookland/invalid_isbn.rb +4 -0
- data/lib/bookland/isbn.rb +13 -128
- data/lib/bookland/isbn_10.rb +37 -0
- data/lib/bookland/version.rb +1 -1
- data/test/bookland_test.rb +95 -0
- data/test/isbns +100 -0
- metadata +22 -28
- data/spec/bookland/isbn_spec.rb +0 -165
- data/spec/bookland_spec.rb +0 -7
- data/spec/fixtures/isbn +0 -1000
- data/spec/spec_helper.rb +0 -15
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/LICENSE
CHANGED
@@ -1,28 +1,22 @@
|
|
1
|
-
|
2
|
-
All rights reserved.
|
1
|
+
(The MIT License)
|
3
2
|
|
4
|
-
|
5
|
-
modification, are permitted provided that the following conditions
|
6
|
-
are met:
|
3
|
+
Copyright (c) 2012 Hakan Ensari
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
[
|
3
|
+
[![travis] [1]] [2]
|
4
4
|
|
5
|
-
[
|
5
|
+
[Bookland] [3] provides ISBN and EAN classes in Ruby.
|
6
6
|
|
7
|
-
##
|
7
|
+
## Installation
|
8
8
|
|
9
|
-
|
9
|
+
```ruby
|
10
|
+
# Gemfile
|
11
|
+
gem 'bookland', '~> 2.0.0.beta'
|
12
|
+
```
|
10
13
|
|
11
|
-
|
12
|
-
isbn.to_isbn13
|
13
|
-
=> "9780262011532"
|
14
|
-
isbn.valid?
|
15
|
-
=> true
|
14
|
+
## Usage
|
16
15
|
|
17
|
-
|
16
|
+
```ruby
|
17
|
+
include 'bookland'
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
isbn = ISBN.new "9780262011532"
|
20
|
+
isbn.valid? # => true
|
21
|
+
isbn10 = isbn.to_isbn_10
|
22
|
+
isbn10.to_s # => "0262011530"
|
23
|
+
```
|
21
24
|
|
22
|
-
|
23
|
-
=> "0262011530"
|
25
|
+
Alternatively, use utility methods defined on the class level:
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
+
```ruby
|
28
|
+
EAN.valid? '0814916013890' # => true
|
29
|
+
ISBN.valid? '0814916013890' # => false
|
30
|
+
```
|
27
31
|
|
28
|
-
[1]:
|
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 '
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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 => :
|
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 = '
|
10
|
+
s.email = 'hakan.ensari@papercavalier.com'
|
11
11
|
s.homepage = 'https://github.com/hakanensari/bookland'
|
12
|
-
s.summary = %q{
|
13
|
-
s.description = %q{Bookland provides
|
12
|
+
s.summary = %q{EAN and ISBN classes}
|
13
|
+
s.description = %q{Bookland provides EAN and ISBN classes.}
|
14
14
|
|
15
|
-
s.
|
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
data/lib/bookland/ean.rb
ADDED
@@ -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
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
#
|
56
|
-
def
|
57
|
-
raise
|
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
|
-
|
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
|
-
|
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
|
-
#
|
19
|
+
# Whether the ISBN is valid.
|
99
20
|
def valid?
|
100
|
-
|
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
|
data/lib/bookland/version.rb
CHANGED
@@ -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
|