現在の~/bin/isbn.rb. だいぶ古い。 situated scriptなのでprefixが978で決め打ちだったりと暗黙の制約がある。
#!/usr/bin/ruby
# license: public domain
module ISBN
module_function
def normalize(src)
src.gsub(/[^0-9X]/, '')
end
def calc_cd_13(src)
digits = normalize(src)[0..11].split(//).map{|n| n.to_i}
raise "digits not 12 but was #{digits}" unless digits.size == 12
sum = 0
digits.each_with_index{|d, i|
coef = if i.odd? then 3; else 1; end
sum = sum + (d * coef)
}
cdnum = 10 - sum.divmod(10).last
case cdnum
when 0 then "0"
when 10 then "0"
else cdnum.to_s
end
end
def calc_cd_10(src)
digits = normalize(src)[0..8].split(//).map{|n| n.to_i}
raise "digits not 9 but was #{digits}" unless digits.size == 9
sum = 0
digits.each_with_index{|d, i|
sum = sum + (d * (10 - i))
}
cdnum = 11 - sum.divmod(11).last
case cdnum
when 10 then "X"
when 11 then "0"
else cdnum.to_s
end
end
def calc_cd(src, n = :unspecified)
case n
when 13 then calc_cd_13(src)
when 10 then calc_cd_10(src)
when :unspecified
normalized = normalize(src)
len = normalized.length
case len
when 13 then calc_cd_13(normalized)
when 12 then calc_cd_13(normalized)
when 10 then calc_cd_10(normalized)
when 9 then calc_cd_10(normalized)
else
raise "cannot guess ISBN-13 or ISBN-10 from length #{len} (#{src})"
end
else
raise "unknown digit specified: #{n}"
end
end
def validate_length(src)
len = normalize(src).length
case
when (len != 10 and len != 13) then return false
else return src
end
end
def validate_cd(src)
normalized = normalize(src)
if normalized.split(//).last == calc_cd(normalized)
calc_cd(normalized)
else
false
end
end
def validate(src)
(validate_length(src) and validate_cd(src))
end
def isbn(src, n = 13)
raise "invalid src length: #{src}" unless validate_length(src)
normalized = normalize(src)
dummy_cd = "0"
case n
when 13
case normalized.length
when 13 then without_cd = normalized.chop
when 10 then without_cd = "978" + normalized.chop
else
raise "invalid digits size: #{normalized.length} for #{src}"
end
without_cd + calc_cd_13(without_cd + dummy_cd)
when 10
case normalized.length
when 13 then without_cd = normalized[3..-1].chop
when 10 then without_cd = normalized.chop
else
raise "invalid digits size: #{normalized.length} for #{src}"
end
without_cd + calc_cd_10(without_cd + dummy_cd)
else
raise "invalid digits size #{normalized.length} specified for #{src}"
end
end
end
if $0 == __FILE__
require 'optparse'
default_config = {
:digit => 13,
:test => false
}
clo = command_line_options = {}
ARGV.options {|o|
o.def_option('--digit=NUM', 'specify output digit'){|n| clo[:digit] = n.to_i}
o.def_option('--test', 'run test'){clo[:test] = true}
o.def_option('--help', 'show this message'){puts o; exit(0)}
o.parse!
} or exit(1)
config = default_config.update(clo)
if config[:test]
require 'test/unit'
class TC_ISBN < Test::Unit::TestCase
def setup
@isbn10ok1 = "ISBN4-10-109205-2"
@isbn10ng1 = "ISBN4-10-109205-3"
@isbn13ok1 = "ISBN978-4-10-109205-8"
@isbn13ng1 = "ISBN978-4-10-109205-9"
@isbn10ok2 = "4274067335"
@isbn10ng2 = "4274067336"
@isbn13ok2 = "9784274067334"
@isbn13ng2 = "9784274067337"
@isbns = [@isbn10ok1, @isbn10ng1, @isbn13ok1, @isbn13ng1,
@isbn10ok2, @isbn10ng2, @isbn13ok2, @isbn13ng2]
end
def test_calc_cd
expected = ["2", "2", "8", "8", "5", "5", "4", "4"]
result = @isbns.map{|s| ISBN.calc_cd(s)}
assert_equal(expected, result)
end
def test_validate
expected = ["2", false, "8", false, "5", false, "4", false]
result = @isbns.map{|s| ISBN.validate(s)}
assert_equal(expected, result)
end
def test_isbn_13
expected = ["9784101092058",
"9784101092058",
"9784101092058",
"9784101092058",
"9784274067334",
"9784274067334",
"9784274067334",
"9784274067334"]
result = @isbns.map{|s| ISBN.isbn(s, 13)}
assert_equal(expected, result)
end
def test_isbn_10
expected = ["4101092052",
"4101092052",
"4101092052",
"4101092052",
"4274067335",
"4274067335",
"4274067335",
"4274067335"]
result = @isbns.map{|s| ISBN.isbn(s, 10)}
assert_equal(expected, result)
end
end
else
result = ISBN.isbn(ARGF.read, config[:digit])
if $stdout.tty?
puts result
else
print result
end
end
end
使い方。標準入出力でやり取りする。
% isbn.rb --help
Usage: isbn [options]
--digit=NUM specify output digit
--test run test
--help show this message
% echo 1234567890 | isbn.rb --digit 10
123456789X
% echo 1234567890 | isbn.rb --digit 13
9781234567897
% echo 1234567890 | isbn.rb
9781234567897
Emacsからはこんな感じで使う。 ISBN13を囲んでこうするとISBN10に置き換わる。
(call-process-region
(region-beginning) (region-end)
"ruby"
t; DELETE
t; BUFFER
nil; DISPLAY
"/home/johnd/bin/isbn.rb" "--digit=10")
Other Articles
- 13 Oct 2017: 『テスト駆動開発』
- 19 Oct 2016: 『新装版 達人プログラマー 職人から名匠への道』
- 19 Aug 2016: 『プログラミングElixir』
- 20 Oct 2015: Migrating from git-media to git-lfs
- 04 Oct 2015: Git Large File Storageクライアントのインストール
- 22 Apr 2015: 「なるのか、なすのか?」(To Be Or To Do?)
- 28 Nov 2014: 『Rubyのしくみ Ruby Under a Microscope』