Skip to content

Instantly share code, notes, and snippets.

@fbwright
Last active August 29, 2015 14:13
Show Gist options
  • Save fbwright/a26c65c74f9283aaeccf to your computer and use it in GitHub Desktop.
Save fbwright/a26c65c74f9283aaeccf to your computer and use it in GitHub Desktop.
ISBN Validator
## [2015-01-12] Challenge #197 [Easy] ISBN Validator
## 2015-01-21 15:08 - by Frankie Brogan
import strutils, parseopt2, version, strfmt, math, unittest
type
TISBNKind* = enum
ISBN10, ISBN13
TISBN* = object
case kind*: TISBNKind
of ISBN10: isbn_10*: array[0..9, int]
of ISBN13: isbn_13*: array[0..12, int]
const
Testing: bool = false
ProgramName: string = "ISBN Validator"
ProgramVersion: TVersion = (0, 1, 7, versionAlpha)
Documentation: string = """
ISBN Validator
Checks whether the given ISBNs are valid numbers or not.
Usage:
isbn check <isbn>...
isbn convert <isbn>...
isbn generate [<n>]
isbn (-h | --help)
isbn (-v | --version)
Options:
-h --help Show this screen.
-v --version Show version.
"""
proc parseInt(c: char): int =
return int(c) - int('0')
proc `$`*(isbn: TISBN): string =
case isbn.kind
of ISBN10:
isbn.isbn_10[0..8].map(proc(x: int): string = $x).join("") &
(if isbn.isbn_10[9] == 10: "X" else: $isbn.isbn_10[9])
of ISBN13:
isbn.isbn_13[0..11].map(proc(x: int): string = $x).join("") &
(if isbn.isbn_13[12] == 10: "0" else: $isbn.isbn_13[12])
proc newISBN*(code: string): TISBN =
let s = code.
toLower().
replace(sub = " ", by = "").
replace(sub = "-", by = "")
case s.len()
of 10:
result.kind = ISBN10
for i in 0..8:
result.isbn_10[i] = parseInt(s[i])
result.isbn_10[9] = if s[9] == 'x': 10 else: parseInt(s[9])
of 13:
result.kind = ISBN13
for i in 0..11:
result.isbn_13[i] = parseInt(s[i])
result.isbn_13[12] = if s[12] == '0': 10 else: parseInt(s[12])
else:
raise newException(ValueError, s & " is not a valid ISBN number.")
proc calculate_checksum*(isbn: TISBN): int =
case isbn.kind
of ISBN10:
let code = isbn.isbn_10
for i in 0..8:
result += code[i] * (10 - i)
result = (11 - (result mod 11)) mod 11
of ISBN_13:
let code = isbn.isbn_13
for i in 0..11:
result += code[i] * (if i mod 2 == 0: 1 else: 3)
result = 10 - (result mod 10)
proc toISBN13*(isbn: TISBN): TISBN =
if isbn.kind == ISBN13:
return isbn
result.kind = ISBN13
result.isbn_13[0] = 9
result.isbn_13[1] = 7
result.isbn_13[2] = 8
for i in 0..8:
result.isbn_13[i+3] = isbn.isbn_10[i]
result.isbn_13[12] = result.calculate_checksum()
proc generateISBN*(kind: TISBNKind = ISBN10): TISBN =
result.kind = kind
case kind
of ISBN10:
for i in 0..8:
result.isbn_10[i] = random(10)
result.isbn_10[9] = calculate_checksum(result)
of ISBN13:
for i in 0..11:
result.isbn_13[i] = random(10)
result.isbn_13[12] = calculate_checksum(result)
proc checksum*(isbn: TISBN): int =
case isbn.kind
of ISBN10: isbn.isbn10[9]
of ISBN13: isbn.isbn13[12]
proc validate*(isbn: TISBN): bool =
isbn.calculate_checksum() == isbn.checksum()
proc writeHelp() =
echo(Documentation)
proc writeVersion() =
echo(ProgramName, " - Version ", ProgramVersion)
when isMainModule:
type
TCommand = enum
cmdNone, cmdError, cmdHelpOrVersion, cmdCheck, cmdConvert, cmdGenerate
var
isbns: seq[TISBN] = @[]
number: int = 0
command: TCommand = cmdNone
for kind, key, val in getopt():
case kind
of cmdArgument:
case command
of cmdNone:
command = case key
of "check": cmdCheck
of "convert": cmdConvert
of "generate": cmdGenerate
else: cmdError
of cmdCheck, cmdConvert:
try:
let isbn: TISBN = newISBN(key)
isbns.add(isbn)
except ValueError:
echo("Error: $1 is not a valid ISBN number." % [key])
of cmdGenerate:
if number <= 0:
number = parseInt(key)
if number <= 0:
number = 1
of cmdError, cmdHelpOrVersion:
discard
of cmdLongOption, cmdShortOption:
case key
of "help", "h":
command = cmdHelpOrVersion
writeHelp()
of "version", "v":
command = cmdHelpOrVersion
writeVersion()
of cmdEnd:
assert(false)
case command
of cmdNone, cmdError:
writeHelp()
of cmdHelpOrVersion:
discard
of cmdCheck:
for isbn in isbns:
printlnfmt("{0: <75}[{1}]", $isbn, if isbn.validate(): "OK" else: " ")
of cmdConvert:
for isbn in isbns:
echo(isbn.toISBN13())
of cmdGenerate:
if number <= 0:
number = 1
randomize()
for i in 1..number:
echo(generateISBN())
import unittest, isbn
const
strings_isbn_10 = [
"097522980X", "0306406152", "1566199093"]
expected_isbn_10 = [
[0, 9, 7, 5, 2, 2, 9, 8, 0, 10], [0, 3, 0, 6, 4, 0, 6, 1, 5, 2],
[1, 5, 6, 6, 1, 9, 9, 0, 9, 3]]
strings_isbn_13 = [
"9781566199094"]
expected_isbn_13 = [
[9, 7, 8, 1, 5, 6, 6, 1, 9, 9, 0, 9, 4]]
suite "Testing newISBN":
test "newISBN - ISBN10":
for i in 0..strings_isbn_10.high:
let isbn = newISBN(strings_isbn_10[i])
check:
isbn.kind == ISBN10
isbn.isbn_10 == expected_isbn_10[i]
test "newISBN - ISBN13":
for i in 0..strings_isbn_13.high:
let isbn = newISBN(strings_isbn_13[i])
check:
isbn.kind == ISBN13
isbn.isbn_13 == expected_isbn_13[i]
suite "Main suite":
setup:
var isbns_10, isbns_13: seq[TISBN]
isbns_10 = @[]
isbns_13 = @[]
for isbn in strings_isbn_10:
isbns_10.add(newISBN(isbn))
for isbn in strings_isbn_13:
isbns_13.add(newISBN(isbn))
teardown:
discard
test "`$` - ISBN10":
for i in 0..isbns_10.high:
check: $isbns_10[i] == strings_isbn_10[i]
test "`$` - ISBN10":
for i in 0..isbns_13.high:
check: $isbns_13[i] == strings_isbn_13[i]
test "validate - ISBN10":
for isbn in isbns_10:
check validate(isbn)
test "validate - ISBN10":
for isbn in isbns_13:
check validate(isbn)
type
TVersionPhase* = enum
versionPreAlpha, versionAlpha, versionBeta, versionReleaseCandidate
TVersion* = tuple[
major, minor, release: int,
phase: TVersionPhase]
proc `$`*(phase: TVersionPhase): string =
case phase
of versionPreAlpha: "-pre-alpha"
of versionAlpha: "-alpha"
of versionBeta: "-beta"
else: ""
proc `$`*(version: TVersion): string =
result = $version.major & "." &
$version.minor & "." &
$version.release &
$version.phase
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment