1use std::str::FromStr;
2
3use thiserror::Error;
4
5pub struct OLKS {
7 keyspace: &'static str,
8 codechar: char,
9}
10
11#[derive(Error, Debug)]
13pub enum OLKeyError {
14 #[error("could not parse {0}: {1}")]
15 InvalidFormat(String, String),
16 #[error("bad keyspace for ‘{0}’, expected {1}")]
17 BadKeyspace(String, &'static str),
18 #[error("trailing code character mismatch")]
19 InvalidCodeChar,
20}
21
22pub const KS_AUTHOR: OLKS = OLKS {
23 keyspace: "authors",
24 codechar: 'A',
25};
26#[allow(dead_code)]
27pub const KS_WORK: OLKS = OLKS {
28 keyspace: "works",
29 codechar: 'W',
30};
31#[allow(dead_code)]
32pub const KS_EDITION: OLKS = OLKS {
33 keyspace: "editions",
34 codechar: 'M',
35};
36
37struct OLKey<'a> {
38 keyspace: &'a str,
39 codechar: char,
40 id: u32,
41}
42
43peg::parser! {
44 grammar key_parser() for str {
45 rule lcchar() = quiet!{['a'..='z']}
46 rule lcword() -> &'input str = s:$(lcchar()+) {s}
47 rule digit() = quiet!{['0'..='9']}
48 rule number() -> u32 = n:$(digit()+) {?
49 u32::from_str(n).or(Err("invalid number"))
50 }
51
52 pub rule ol_key() -> OLKey<'input>
53 = "/" ks:lcword() "/OL" id:number() c:['A'..='Z'] {
54 OLKey {
55 keyspace: ks,
56 codechar: c,
57 id
58 }
59 }
60 }
61}
62
63pub fn parse_ol_key(key: &str, ks: OLKS) -> Result<u32, OLKeyError> {
65 let k = key_parser::ol_key(key)
66 .map_err(|e| OLKeyError::InvalidFormat(key.to_string(), format!("{:?}", e)))?;
67 if k.codechar != ks.codechar {
68 Err(OLKeyError::InvalidCodeChar)
69 } else if k.keyspace != ks.keyspace && k.keyspace[0..1] != ks.keyspace[0..1] {
70 Err(OLKeyError::BadKeyspace(key.to_string(), ks.keyspace))
71 } else {
72 Ok(k.id)
73 }
74}
75
76#[test]
77fn test_parse_work() {
78 let id = parse_ol_key("/works/OL38140W", KS_WORK).expect("parse failed");
79 assert_eq!(id, 38140);
80}