mirror of
https://git.pumpin.online/funkpower/catena.git
synced 2026-10-23 03:07:13 +00:00
idnorm and bibfetch: an identifier read, and a register asked
This commit is contained in:
@@ -385,6 +385,24 @@ pub unsafe extern "C" fn catena_series_of(
|
||||
}
|
||||
}
|
||||
|
||||
/// Read an identifier out of whatever it arrived wrapped in. Returns a JSON
|
||||
/// record, or NULL when the string is not one — an answer, not an error.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `text` must be NUL-terminated UTF-8.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn catena_parse_identifier(text: *const c_char) -> *mut c_char {
|
||||
clear_error();
|
||||
let Some(text) = (unsafe { borrow(text, "text") }) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
match crate::parse_identifier(text) {
|
||||
Some(found) => give_json(&found),
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -496,6 +514,17 @@ mod tests {
|
||||
assert!(json.contains("\"volume\":3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_identifier_is_normalised_across_the_boundary() {
|
||||
let text = c"https://doi.org/10.1000/182";
|
||||
let json = through(|| unsafe { catena_parse_identifier(text.as_ptr()) }).expect("a record");
|
||||
assert!(json.contains("\"id\":\"10.1000/182\""), "{json}");
|
||||
|
||||
let nope = c"hello";
|
||||
assert!(through(|| unsafe { catena_parse_identifier(nope.as_ptr()) }).is_none());
|
||||
assert_eq!(error(), None, "not an identifier is an answer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_abi_version_is_the_one_the_header_declares() {
|
||||
assert_eq!(catena_abi_version(), 1);
|
||||
|
||||
@@ -176,6 +176,12 @@ pub fn identify(name: &str, bytes: &[u8]) -> Doc {
|
||||
booksniff::sniff(&bytes, name)
|
||||
}
|
||||
|
||||
/// Read an identifier — a DOI, ISSN, ISBN or arXiv id — out of whatever it
|
||||
/// arrived wrapped in.
|
||||
pub fn parse_identifier(text: &str) -> Option<idnorm::Identifier> {
|
||||
idnorm::parse(text)
|
||||
}
|
||||
|
||||
/// The series a title belongs to, and which volume of it this is.
|
||||
pub fn series_of(title: &str, author: Option<&str>) -> Option<seriesof::Proposal> {
|
||||
seriesof::series_of(title, author)
|
||||
@@ -237,6 +243,13 @@ mod tests {
|
||||
assert_eq!(channel_named("lexical"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_identifier_comes_back_normalised() {
|
||||
let found = parse_identifier("https://doi.org/10.1000/182").expect("an identifier");
|
||||
assert_eq!(found.id, "10.1000/182");
|
||||
assert_eq!(parse_identifier("hello"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_series_comes_back_the_same_as_the_library_reads_it() {
|
||||
let found = series_of("The Philokalia Vol 3", None).expect("a series");
|
||||
|
||||
@@ -208,6 +208,17 @@ fn series_of<'py>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Read an identifier out of whatever it arrived wrapped in.
|
||||
///
|
||||
/// `None` when the string is not one.
|
||||
#[pyfunction]
|
||||
fn parse_identifier<'py>(py: Python<'py>, text: &str) -> PyResult<Option<Bound<'py, PyAny>>> {
|
||||
match crate::parse_identifier(text) {
|
||||
Some(found) => Ok(Some(to_py(py, &found)?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// The module Python imports.
|
||||
#[pymodule]
|
||||
fn _catena(module: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
@@ -221,5 +232,6 @@ fn _catena(module: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
module.add_function(wrap_pyfunction!(fuse_hits, module)?)?;
|
||||
module.add_function(wrap_pyfunction!(identify, module)?)?;
|
||||
module.add_function(wrap_pyfunction!(series_of, module)?)?;
|
||||
module.add_function(wrap_pyfunction!(parse_identifier, module)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -70,6 +70,21 @@ export interface Hit {
|
||||
payload: unknown;
|
||||
}
|
||||
|
||||
/** A DOI, ISSN, ISBN or arXiv identifier, read and checked. */
|
||||
export interface Identifier {
|
||||
v: number;
|
||||
/** The text it was read from. */
|
||||
input: string;
|
||||
/** "doi", "issn", "isbn" or "arxiv". */
|
||||
kind: string;
|
||||
/** The identifier, in the form it should be stored and compared in. */
|
||||
id: string;
|
||||
/** Whether the check digit agrees, or null where the kind carries none. */
|
||||
checksum: boolean | null;
|
||||
/** A URL that resolves it. */
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/** What a title says about its series. */
|
||||
export interface SeriesProposal {
|
||||
v: number;
|
||||
@@ -233,6 +248,17 @@ pub fn series_of(title: &str, author: Option<String>) -> Result<JsValue, JsValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Read an identifier out of whatever it arrived wrapped in.
|
||||
///
|
||||
/// `undefined` when the string is not one.
|
||||
#[wasm_bindgen(js_name = parseIdentifier, skip_typescript)]
|
||||
pub fn parse_identifier(text: &str) -> Result<JsValue, JsValue> {
|
||||
match crate::parse_identifier(text) {
|
||||
Some(found) => to_js(&found),
|
||||
None => Ok(JsValue::UNDEFINED),
|
||||
}
|
||||
}
|
||||
|
||||
/// Declarations for the functions whose return type the generator cannot
|
||||
/// infer, so a caller gets `Chunk[]` rather than `any`.
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
@@ -244,4 +270,5 @@ export function splitCollection(markdown: string): Section[];
|
||||
export function fuseHits(hits: Hit[], k?: number, weights?: Record<string, number>, limit?: number): Hit[];
|
||||
export function identify(name: string, data: Uint8Array): Doc;
|
||||
export function seriesOf(title: string, author?: string): SeriesProposal | undefined;
|
||||
export function parseIdentifier(text: string): Identifier | undefined;
|
||||
"#;
|
||||
|
||||
Reference in New Issue
Block a user