├── .gitignore ├── Cargo.toml ├── add_check_digit.py ├── CHANGELOG.md ├── Cargo.lock ├── src ├── main.rs └── sierra.rs ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "add_sierra_check_digit" 3 | version = "1.0.1" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | substring = "1.4.0" 8 | -------------------------------------------------------------------------------- /add_check_digit.py: -------------------------------------------------------------------------------- 1 | def add_check_digit(id_str): 2 | total = sum( 3 | i * int(digit) 4 | for i, digit in enumerate(reversed(id_str), start=2) 5 | ) 6 | 7 | remainder = total % 11 8 | 9 | if remainder == 10: 10 | return id_str + 'x' 11 | else: 12 | return id_str + str(remainder) 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.0.1 (2021-09-19) 4 | 5 | Don't print a newline after the result. 6 | This is a bit simpler if I'm saving a Sierra number directly to the clipboard, e.g. 7 | 8 | ```console 9 | $ add_sierra_check_digit b1287325 | pbcopy 10 | ``` 11 | 12 | Previously this would have a trailing newline I had to remove; now it's just the raw number. 13 | 14 | ## v1.0.0 (2021-09-04) 15 | 16 | Initial release. 17 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "add_sierra_check_digit" 7 | version = "1.0.1" 8 | dependencies = [ 9 | "substring", 10 | ] 11 | 12 | [[package]] 13 | name = "autocfg" 14 | version = "1.0.1" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 17 | 18 | [[package]] 19 | name = "substring" 20 | version = "1.4.5" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" 23 | dependencies = [ 24 | "autocfg", 25 | ] 26 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod sierra; 2 | 3 | use std::env; 4 | 5 | use substring::Substring; 6 | 7 | fn main() { 8 | let args: Vec = env::args().collect(); 9 | if args.len() == 2 { 10 | let number = &args[1]; 11 | 12 | if number.len() == 7 && sierra::is_numeric(&number) { 13 | print!("{}{}", number, sierra::get_check_digit(&number)); 14 | } else if number.len() == 8 && sierra::is_numeric(&number.substring(1, 8)) { 15 | let record_type_prefix = number.substring(0, 1); 16 | let record_number = number.substring(1, 8); 17 | print!("{}{}{}", record_type_prefix, record_number, sierra::get_check_digit(&record_number)); 18 | } else { 19 | eprintln!("Not a valid Sierra number: {}", number); 20 | std::process::exit(1); 21 | } 22 | } else { 23 | eprintln!("Usage: {} ", &args[0]); 24 | std::process::exit(1); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Alex Chan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 17 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 19 | OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/sierra.rs: -------------------------------------------------------------------------------- 1 | pub fn is_numeric(s: &str) -> bool { 2 | s.chars().all(|c| '0' <= c && c <= '9') 3 | } 4 | 5 | /// Adds the check digit to a Sierra record number. 6 | /// 7 | /// This function takes the seven-digit record number and returns the 8 | /// check digit that goes with this record number. 9 | /// 10 | pub fn get_check_digit(record_number: &str) -> String { 11 | 12 | assert!(record_number.len() == 7); 13 | assert!(is_numeric(record_number)); 14 | 15 | // Quoting from the Sierra manual: 16 | // 17 | // Check digits may be any one of 11 possible digits (0, 1, 2, 3, 4, 18 | // 5, 6, 7, 8, 9, or x). 19 | // 20 | // The check digit is calculated as follows: 21 | // 22 | // Multiply the rightmost digit of the record number by 2, the next 23 | // digit to the left by 3, the next by 4, etc., and total the products. 24 | // 25 | // Divide the total by 11 and retain the remainder. The remainder 26 | // after the division is the check digit. If the remainder is 10, 27 | // the letter x is used as the check digit. 28 | // 29 | // See https://documentation.iii.com/sierrahelp/Default.htm#sril/sril_records_numbers.html 30 | // 31 | let chars_in_reverse = 32 | record_number 33 | .chars() 34 | .map(|c| c.to_digit(10).unwrap()) 35 | .rev(); 36 | 37 | let multiply_by = vec!(2, 3, 4, 5, 6, 7, 8); 38 | 39 | let total: u32 = 40 | chars_in_reverse.zip(multiply_by) 41 | .map(|(c, m)| c * m) 42 | .sum(); 43 | 44 | let remainder = total % 11; 45 | 46 | if remainder == 10 { 47 | String::from("x") 48 | } else { 49 | remainder.to_string() 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use crate::sierra::{get_check_digit, validate}; 56 | 57 | #[test] 58 | fn test_get_check_digit() { 59 | // This is an example from the Sierra documentation 60 | assert_eq!(get_check_digit("1024364"), "1"); 61 | 62 | // Test the case where the remainder is an 'x' 63 | assert_eq!(get_check_digit("1026579"), "x"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # add-sierra-check-digit 2 | 3 | This is a command-line tool for getting the check digit of a Sierra record number. 4 | 5 | At [Wellcome], we use a library management system called [Sierra]. 6 | Within Sierra, records are identified by a [record number][record number], which is a seven-digit number with an optional record type prefix and modulo 11 check digit. 7 | 8 | For example: 9 | 10 | * `1234567` is a record number 11 | * `12345672` is a record with a check digit 12 | * `b12345672` is a record with record type prefix and a check digit (here the "b" prefix means this is a bibliographic record) 13 | 14 | I prefer to use the long form, with the record type prefix and check digit. 15 | It avoids ambiguity, and it's simpler for library staff to deal with. 16 | It's easy to go from the long form to the short form, but not vice versa. 17 | 18 | The Sierra documentation explains how to [work out the check digit][check digit]; this is a command-line tool works it out for me. 19 | For example: 20 | 21 | ```console 22 | $ add_sierra_check_digit 1234567 23 | 12345672 24 | ``` 25 | 26 | Here the check digit is "2". 27 | 28 | [Wellcome]: https://github.com/wellcomecollection 29 | [Sierra]: https://www.iii.com/products/sierra-ils/ 30 | [record number]: https://documentation.iii.com/sierrahelp/Default.htm#sril/sril_records_numbers.html 31 | [check digit]: https://documentation.iii.com/sierrahelp/Default.htm#sril/sril_records_numbers.html#check_digit 32 | 33 | ## Usage 34 | 35 | You can add a check digit to a seven-digit record number: 36 | 37 | ```console 38 | $ add_sierra_check_digit 1962791 39 | 19627919 40 | ``` 41 | 42 | You can also add a check digit to a record number with a record type prefix, which will be preserved: 43 | 44 | ```console 45 | $ add_sierra_check_digit i1962791 46 | i19627919 47 | ``` 48 | 49 | Note that the check digit may not always be numeric: sometimes it's an "x". 50 | 51 | ```console 52 | $ add_sierra_check_digit 1026579 53 | 1026579x 54 | ``` 55 | 56 | ## Installation 57 | 58 | You'll need Rust installed, then clone the repo and install it as a standard Rust binary: 59 | 60 | ```console 61 | $ git clone git@github.com:alexwlchan/add_sierra_check_digit.git 62 | $ cd add_sierra_check_digit 63 | $ cargo install --path . 64 | ``` 65 | 66 | ## Other implementations 67 | 68 | I wrote this in Rust because this was a small, self-contained project where I could try Rust and get something super whizzy fast. 69 | 70 | I've implemented the check digit algorithm in two other languages: [Python](add_check_digit.py) and [Scala][scala]. 71 | 72 | [scala]: https://github.com/wellcomecollection/scala-libs/blob/86d25fff221e9f918c819a0db5ff4673da174101/sierra/src/main/scala/weco/sierra/models/identifiers/TypedSierraRecordNumber.scala#L27-L52 73 | 74 | ## License 75 | 76 | MIT. 77 | --------------------------------------------------------------------------------