csan 0.0.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE-APACHE +201 -0
- data/LICENSE-MIT +8 -0
- data/README.md +56 -26
- data/exe/csan +7 -0
- data/ext/csan/Cargo.lock +566 -0
- data/ext/csan/Cargo.toml +130 -0
- data/ext/csan/extconf.rb +12 -0
- data/ext/csan/src/lib.rs +263 -0
- data/lib/csan/version.rb +3 -1
- data/lib/csan.rb +90 -2
- data/sig/csan.rbs +54 -1
- metadata +39 -9
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -4
data/ext/csan/src/lib.rs
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// The magnus macro expansions contain `unsafe`, so
|
|
2
|
+
// `#![forbid(unsafe_code)]` (which cannot be overridden) is not usable
|
|
3
|
+
// here. `deny` still rejects any hand-written `unsafe` in this crate
|
|
4
|
+
// while allowing the macro-generated blocks through.
|
|
5
|
+
#![deny(unsafe_code)]
|
|
6
|
+
|
|
7
|
+
//! The `csan` gem's native extension, `Csan::Native`.
|
|
8
|
+
//!
|
|
9
|
+
//! Exposes [`csan_core`]'s Cutter call number generation to Ruby, plus the
|
|
10
|
+
//! `run_cli` entry point behind the gem's `csan` executable (the same
|
|
11
|
+
//! `clap` interface as the standalone binary, provided by [`csan`]).
|
|
12
|
+
//! The user-facing API (keyword arguments, documentation) lives in
|
|
13
|
+
//! `lib/csan.rb`, which calls these with positional arguments.
|
|
14
|
+
|
|
15
|
+
use core::fmt;
|
|
16
|
+
use core::iter;
|
|
17
|
+
use csan_core::{
|
|
18
|
+
Attribution, CutterConfig, CutterGenerator as _, CutterLoc, CutterLocMethod, CutterSanborn,
|
|
19
|
+
CutterSanbornMethod, detailed_json,
|
|
20
|
+
};
|
|
21
|
+
use magnus::value::ReprValue as _;
|
|
22
|
+
use magnus::{Error, IntoValue as _, RHash, Ruby, Value};
|
|
23
|
+
use serde_json::Value as Json;
|
|
24
|
+
|
|
25
|
+
/// Builds the [`Attribution`] for a set of optional inputs, or Ruby's
|
|
26
|
+
/// `ArgumentError`.
|
|
27
|
+
fn attribution(
|
|
28
|
+
ruby: &Ruby,
|
|
29
|
+
forename: Option<&str>,
|
|
30
|
+
surname: Option<&str>,
|
|
31
|
+
title: Option<&str>,
|
|
32
|
+
) -> Result<Attribution, Error> {
|
|
33
|
+
Attribution::from_parts(forename, surname, title).ok_or_else(|| {
|
|
34
|
+
Error::new(
|
|
35
|
+
ruby.exception_arg_error(),
|
|
36
|
+
"provide at least one non-blank input: forename, surname, or title",
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Maps a failure to Ruby's `ArgumentError`, prefixed with `context`.
|
|
42
|
+
fn argument_error(ruby: &Ruby, context: &str, error: &impl fmt::Display) -> Error {
|
|
43
|
+
Error::new(ruby.exception_arg_error(), format!("{context}: {error}"))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Generates the Cutter-Sanborn value shared by both `cutter_sanborn`
|
|
47
|
+
/// variants.
|
|
48
|
+
fn sanborn(
|
|
49
|
+
ruby: &Ruby,
|
|
50
|
+
forename: Option<&str>,
|
|
51
|
+
surname: Option<&str>,
|
|
52
|
+
title: Option<&str>,
|
|
53
|
+
prefix_len: Option<usize>,
|
|
54
|
+
suffix_len: Option<usize>,
|
|
55
|
+
) -> Result<CutterSanborn, Error> {
|
|
56
|
+
let entry = attribution(ruby, forename, surname, title)?;
|
|
57
|
+
let config = CutterConfig::from_overrides(prefix_len, suffix_len, None);
|
|
58
|
+
CutterSanbornMethod::generate(&entry, config)
|
|
59
|
+
.map_err(|error| argument_error(ruby, "cutter generation failed", &error))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Generates the `LoC` value shared by both `cutter_loc` variants.
|
|
63
|
+
fn loc(
|
|
64
|
+
ruby: &Ruby,
|
|
65
|
+
forename: Option<&str>,
|
|
66
|
+
surname: Option<&str>,
|
|
67
|
+
title: Option<&str>,
|
|
68
|
+
max_len: Option<usize>,
|
|
69
|
+
) -> Result<CutterLoc, Error> {
|
|
70
|
+
let entry = attribution(ruby, forename, surname, title)?;
|
|
71
|
+
let config = CutterConfig::from_overrides(None, None, max_len);
|
|
72
|
+
CutterLocMethod::generate(&entry, config)
|
|
73
|
+
.map_err(|error| argument_error(ruby, "cutter generation failed", &error))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Converts a JSON value produced by [`detailed_json`] into the equivalent
|
|
77
|
+
/// Ruby object: objects become hashes with symbol keys.
|
|
78
|
+
///
|
|
79
|
+
/// The detailed objects only ever contain strings, integers, nested objects,
|
|
80
|
+
/// and JSON null (for an absent work mark), so those are the cases that
|
|
81
|
+
/// matter; other shapes are mapped on a best-effort basis to keep the
|
|
82
|
+
/// function total.
|
|
83
|
+
fn json_to_ruby(ruby: &Ruby, value: &Json) -> Result<Value, Error> {
|
|
84
|
+
Ok(match value {
|
|
85
|
+
Json::Null => ruby.qnil().as_value(),
|
|
86
|
+
Json::Bool(boolean) => boolean.into_value_with(ruby),
|
|
87
|
+
Json::String(string) => string.as_str().into_value_with(ruby),
|
|
88
|
+
Json::Number(number) => {
|
|
89
|
+
if let Some(unsigned) = number.as_u64() {
|
|
90
|
+
unsigned.into_value_with(ruby)
|
|
91
|
+
} else if let Some(signed) = number.as_i64() {
|
|
92
|
+
signed.into_value_with(ruby)
|
|
93
|
+
} else if let Some(float) = number.as_f64() {
|
|
94
|
+
float.into_value_with(ruby)
|
|
95
|
+
} else {
|
|
96
|
+
// No numeric representation fits; fall back to the text form.
|
|
97
|
+
number.to_string().into_value_with(ruby)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
Json::Array(items) => {
|
|
101
|
+
let array = ruby.ary_new_capa(items.len());
|
|
102
|
+
for item in items {
|
|
103
|
+
array.push(json_to_ruby(ruby, item)?)?;
|
|
104
|
+
}
|
|
105
|
+
array.as_value()
|
|
106
|
+
}
|
|
107
|
+
Json::Object(_) => detailed_hash(ruby, value)?.as_value(),
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Builds the detailed hash from a cutter's [`detailed_json`] object, so
|
|
112
|
+
/// every frontend agrees on the shape.
|
|
113
|
+
///
|
|
114
|
+
/// The value is always a JSON object (the cutter models are structs); a
|
|
115
|
+
/// non-object is treated as a programming error and surfaced as
|
|
116
|
+
/// `ArgumentError`.
|
|
117
|
+
fn detailed_hash(ruby: &Ruby, value: &Json) -> Result<RHash, Error> {
|
|
118
|
+
let Json::Object(fields) = value else {
|
|
119
|
+
return Err(Error::new(
|
|
120
|
+
ruby.exception_arg_error(),
|
|
121
|
+
"detailed cutter value did not serialize as an object",
|
|
122
|
+
));
|
|
123
|
+
};
|
|
124
|
+
let hash = ruby.hash_new();
|
|
125
|
+
for (key, field) in fields {
|
|
126
|
+
hash.aset(ruby.to_symbol(key), json_to_ruby(ruby, field)?)?;
|
|
127
|
+
}
|
|
128
|
+
Ok(hash)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// The Cutter-Sanborn call number as a string (e.g. `"D548o"`).
|
|
132
|
+
#[expect(
|
|
133
|
+
clippy::needless_pass_by_value,
|
|
134
|
+
reason = "magnus passes Ruby's arguments as owned values"
|
|
135
|
+
)]
|
|
136
|
+
fn cutter_sanborn(
|
|
137
|
+
ruby: &Ruby,
|
|
138
|
+
forename: Option<String>,
|
|
139
|
+
surname: Option<String>,
|
|
140
|
+
title: Option<String>,
|
|
141
|
+
prefix_len: Option<usize>,
|
|
142
|
+
suffix_len: Option<usize>,
|
|
143
|
+
) -> Result<String, Error> {
|
|
144
|
+
let cutter = sanborn(
|
|
145
|
+
ruby,
|
|
146
|
+
forename.as_deref(),
|
|
147
|
+
surname.as_deref(),
|
|
148
|
+
title.as_deref(),
|
|
149
|
+
prefix_len,
|
|
150
|
+
suffix_len,
|
|
151
|
+
)?;
|
|
152
|
+
Ok(cutter.to_string())
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// The Cutter-Sanborn number's parts: `alpha_prefix`, `numeric`,
|
|
156
|
+
/// `alpha_suffix` (nil without a work mark), `entry`, and `formatted`.
|
|
157
|
+
#[expect(
|
|
158
|
+
clippy::needless_pass_by_value,
|
|
159
|
+
reason = "magnus passes Ruby's arguments as owned values"
|
|
160
|
+
)]
|
|
161
|
+
fn cutter_sanborn_detailed(
|
|
162
|
+
ruby: &Ruby,
|
|
163
|
+
forename: Option<String>,
|
|
164
|
+
surname: Option<String>,
|
|
165
|
+
title: Option<String>,
|
|
166
|
+
prefix_len: Option<usize>,
|
|
167
|
+
suffix_len: Option<usize>,
|
|
168
|
+
) -> Result<RHash, Error> {
|
|
169
|
+
let cutter = sanborn(
|
|
170
|
+
ruby,
|
|
171
|
+
forename.as_deref(),
|
|
172
|
+
surname.as_deref(),
|
|
173
|
+
title.as_deref(),
|
|
174
|
+
prefix_len,
|
|
175
|
+
suffix_len,
|
|
176
|
+
)?;
|
|
177
|
+
let value = detailed_json(&cutter)
|
|
178
|
+
.map_err(|error| argument_error(ruby, "detailed cutter value failed", &error))?;
|
|
179
|
+
detailed_hash(ruby, &value)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// The Library of Congress cutter number as a string (e.g. `"C378"`).
|
|
183
|
+
#[expect(
|
|
184
|
+
clippy::needless_pass_by_value,
|
|
185
|
+
reason = "magnus passes Ruby's arguments as owned values"
|
|
186
|
+
)]
|
|
187
|
+
fn cutter_loc(
|
|
188
|
+
ruby: &Ruby,
|
|
189
|
+
forename: Option<String>,
|
|
190
|
+
surname: Option<String>,
|
|
191
|
+
title: Option<String>,
|
|
192
|
+
max_len: Option<usize>,
|
|
193
|
+
) -> Result<String, Error> {
|
|
194
|
+
let cutter = loc(
|
|
195
|
+
ruby,
|
|
196
|
+
forename.as_deref(),
|
|
197
|
+
surname.as_deref(),
|
|
198
|
+
title.as_deref(),
|
|
199
|
+
max_len,
|
|
200
|
+
)?;
|
|
201
|
+
Ok(cutter.to_string())
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// The Library of Congress number's parts: `alpha_prefix`, `numeric`, and
|
|
205
|
+
/// `formatted`.
|
|
206
|
+
#[expect(
|
|
207
|
+
clippy::needless_pass_by_value,
|
|
208
|
+
reason = "magnus passes Ruby's arguments as owned values"
|
|
209
|
+
)]
|
|
210
|
+
fn cutter_loc_detailed(
|
|
211
|
+
ruby: &Ruby,
|
|
212
|
+
forename: Option<String>,
|
|
213
|
+
surname: Option<String>,
|
|
214
|
+
title: Option<String>,
|
|
215
|
+
max_len: Option<usize>,
|
|
216
|
+
) -> Result<RHash, Error> {
|
|
217
|
+
let cutter = loc(
|
|
218
|
+
ruby,
|
|
219
|
+
forename.as_deref(),
|
|
220
|
+
surname.as_deref(),
|
|
221
|
+
title.as_deref(),
|
|
222
|
+
max_len,
|
|
223
|
+
)?;
|
|
224
|
+
let value = detailed_json(&cutter)
|
|
225
|
+
.map_err(|error| argument_error(ruby, "detailed cutter value failed", &error))?;
|
|
226
|
+
detailed_hash(ruby, &value)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Runs the `csan` command-line interface on `args` (without the program
|
|
230
|
+
/// name) and returns the process exit code: the engine behind the gem's
|
|
231
|
+
/// `csan` executable.
|
|
232
|
+
fn run_cli(args: Vec<String>) -> u8 {
|
|
233
|
+
csan::run_from_args(iter::once(String::from("csan")).chain(args))
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// The extension's entry point, `Init_csan_rb`, which Ruby calls when
|
|
237
|
+
/// `lib/csan.rb` requires the extension. In a private module:
|
|
238
|
+
/// `#[magnus::init]` exports it whatever its visibility, and it is no public
|
|
239
|
+
/// API to document.
|
|
240
|
+
mod entry {
|
|
241
|
+
use magnus::{Error, Ruby, function, prelude::*};
|
|
242
|
+
|
|
243
|
+
use super::{
|
|
244
|
+
cutter_loc, cutter_loc_detailed, cutter_sanborn, cutter_sanborn_detailed, run_cli,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/// Defines `Csan::Native` and its module functions, and
|
|
248
|
+
/// `Csan::Native::VERSION`, the crate's version.
|
|
249
|
+
#[magnus::init]
|
|
250
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
251
|
+
let native = ruby.define_module("Csan")?.define_module("Native")?;
|
|
252
|
+
native.define_module_function("cutter_sanborn", function!(cutter_sanborn, 5))?;
|
|
253
|
+
native.define_module_function(
|
|
254
|
+
"cutter_sanborn_detailed",
|
|
255
|
+
function!(cutter_sanborn_detailed, 5),
|
|
256
|
+
)?;
|
|
257
|
+
native.define_module_function("cutter_loc", function!(cutter_loc, 4))?;
|
|
258
|
+
native.define_module_function("cutter_loc_detailed", function!(cutter_loc_detailed, 4))?;
|
|
259
|
+
native.define_module_function("run_cli", function!(run_cli, 1))?;
|
|
260
|
+
native.const_set("VERSION", env!("CARGO_PKG_VERSION"))?;
|
|
261
|
+
Ok(())
|
|
262
|
+
}
|
|
263
|
+
}
|
data/lib/csan/version.rb
CHANGED
data/lib/csan.rb
CHANGED
|
@@ -1,8 +1,96 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rbconfig"
|
|
3
4
|
require_relative "csan/version"
|
|
4
5
|
|
|
6
|
+
# A precompiled (platform) gem carries one extension per Ruby minor version,
|
|
7
|
+
# in lib/csan/<major.minor>/; the source gem, and a local `rake compile`,
|
|
8
|
+
# build one in lib/csan/. (Chosen by file, so that an extension that fails to
|
|
9
|
+
# load reports its own error.)
|
|
10
|
+
versioned = File.join(__dir__, "csan", RUBY_VERSION[/\A\d+\.\d+/], "csan_rb.#{RbConfig::CONFIG["DLEXT"]}")
|
|
11
|
+
if File.exist?(versioned)
|
|
12
|
+
require versioned
|
|
13
|
+
else
|
|
14
|
+
require_relative "csan/csan_rb"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Cutter-Sanborn three-figure and Library of Congress cutter numbers for
|
|
18
|
+
# library cataloging, from author names and titles.
|
|
19
|
+
#
|
|
20
|
+
# Every function takes the author's `forename` and `surname` and the work's
|
|
21
|
+
# `title` as keyword arguments, at least one of them non-blank (blank strings
|
|
22
|
+
# count as absent), and raises `ArgumentError` on unusable input. A length
|
|
23
|
+
# that is not an Integer raises `TypeError` (a Float is truncated), and a
|
|
24
|
+
# negative one `RangeError`.
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# Csan.cutter_sanborn(forename: "Charles", surname: "Dickens", title: "Oliver Twist")
|
|
28
|
+
# # => "D548o"
|
|
29
|
+
# Csan.cutter_loc(surname: "Carter") # => "C378"
|
|
5
30
|
module Csan
|
|
6
|
-
|
|
7
|
-
#
|
|
31
|
+
# The Cutter-Sanborn three-figure number.
|
|
32
|
+
#
|
|
33
|
+
# @param forename [String, nil] the author's forename
|
|
34
|
+
# @param surname [String, nil] the author's surname
|
|
35
|
+
# @param title [String, nil] the work's title: the work mark, or the whole
|
|
36
|
+
# number when there is no author
|
|
37
|
+
# @param prefix_len [Integer, nil] letters of the name kept before the
|
|
38
|
+
# number (default 1)
|
|
39
|
+
# @param suffix_len [Integer, nil] letters of the title in the work mark
|
|
40
|
+
# (default 1)
|
|
41
|
+
# @return [String] the number, e.g. "D548o"
|
|
42
|
+
# @raise [ArgumentError] on unusable input
|
|
43
|
+
def self.cutter_sanborn(forename: nil, surname: nil, title: nil, prefix_len: nil, suffix_len: nil)
|
|
44
|
+
Native.cutter_sanborn(forename, surname, title, prefix_len, suffix_len)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The Cutter-Sanborn number's parts.
|
|
48
|
+
#
|
|
49
|
+
# Takes the arguments of {cutter_sanborn}.
|
|
50
|
+
#
|
|
51
|
+
# @return [Hash{Symbol => String, Integer, nil}] `:alpha_prefix`,
|
|
52
|
+
# `:numeric`, `:alpha_suffix` (nil without a work mark), `:entry` (the
|
|
53
|
+
# table entry that matched), and `:formatted`
|
|
54
|
+
# @raise [ArgumentError] on unusable input
|
|
55
|
+
def self.cutter_sanborn_detailed(forename: nil, surname: nil, title: nil, prefix_len: nil, suffix_len: nil)
|
|
56
|
+
Native.cutter_sanborn_detailed(forename, surname, title, prefix_len, suffix_len)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The Library of Congress cutter number.
|
|
60
|
+
#
|
|
61
|
+
# @param forename [String, nil] the author's forename
|
|
62
|
+
# @param surname [String, nil] the author's surname
|
|
63
|
+
# @param title [String, nil] the work's title, cuttered when there is no
|
|
64
|
+
# author
|
|
65
|
+
# @param max_len [Integer, nil] digits in the number (default 3, at most 4)
|
|
66
|
+
# @return [String] the number, e.g. "C378"
|
|
67
|
+
# @raise [ArgumentError] on unusable input
|
|
68
|
+
def self.cutter_loc(forename: nil, surname: nil, title: nil, max_len: nil)
|
|
69
|
+
Native.cutter_loc(forename, surname, title, max_len)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The Library of Congress number's parts.
|
|
73
|
+
#
|
|
74
|
+
# Takes the arguments of {cutter_loc}.
|
|
75
|
+
#
|
|
76
|
+
# @return [Hash{Symbol => String, Integer}] `:alpha_prefix`, `:numeric`,
|
|
77
|
+
# and `:formatted`
|
|
78
|
+
# @raise [ArgumentError] on unusable input
|
|
79
|
+
def self.cutter_loc_detailed(forename: nil, surname: nil, title: nil, max_len: nil)
|
|
80
|
+
Native.cutter_loc_detailed(forename, surname, title, max_len)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Runs the `csan` command-line interface, the engine of the gem's `csan`
|
|
84
|
+
# executable: its options, help, messages, and exit codes are the
|
|
85
|
+
# standalone binary's. It writes to the process's standard output and
|
|
86
|
+
# error directly, after flushing `$stdout` and `$stderr`.
|
|
87
|
+
#
|
|
88
|
+
# @param args [Array<String>] the arguments, without the program name
|
|
89
|
+
# @return [Integer] the exit code: 0, 1 for an unusable input, 2 for a
|
|
90
|
+
# usage error
|
|
91
|
+
def self.run_cli(args)
|
|
92
|
+
$stdout.flush
|
|
93
|
+
$stderr.flush
|
|
94
|
+
Native.run_cli(args)
|
|
95
|
+
end
|
|
8
96
|
end
|
data/sig/csan.rbs
CHANGED
|
@@ -1,4 +1,57 @@
|
|
|
1
|
+
# Type signatures for the csan gem.
|
|
1
2
|
module Csan
|
|
2
3
|
VERSION: String
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
type sanborn_parts = {
|
|
6
|
+
alpha_prefix: String,
|
|
7
|
+
numeric: Integer,
|
|
8
|
+
alpha_suffix: String?,
|
|
9
|
+
entry: String,
|
|
10
|
+
formatted: String
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type loc_parts = { alpha_prefix: String, numeric: Integer, formatted: String }
|
|
14
|
+
|
|
15
|
+
def self.cutter_sanborn: (
|
|
16
|
+
?forename: String?,
|
|
17
|
+
?surname: String?,
|
|
18
|
+
?title: String?,
|
|
19
|
+
?prefix_len: Integer?,
|
|
20
|
+
?suffix_len: Integer?
|
|
21
|
+
) -> String
|
|
22
|
+
|
|
23
|
+
def self.cutter_sanborn_detailed: (
|
|
24
|
+
?forename: String?,
|
|
25
|
+
?surname: String?,
|
|
26
|
+
?title: String?,
|
|
27
|
+
?prefix_len: Integer?,
|
|
28
|
+
?suffix_len: Integer?
|
|
29
|
+
) -> sanborn_parts
|
|
30
|
+
|
|
31
|
+
def self.cutter_loc: (
|
|
32
|
+
?forename: String?,
|
|
33
|
+
?surname: String?,
|
|
34
|
+
?title: String?,
|
|
35
|
+
?max_len: Integer?
|
|
36
|
+
) -> String
|
|
37
|
+
|
|
38
|
+
def self.cutter_loc_detailed: (
|
|
39
|
+
?forename: String?,
|
|
40
|
+
?surname: String?,
|
|
41
|
+
?title: String?,
|
|
42
|
+
?max_len: Integer?
|
|
43
|
+
) -> loc_parts
|
|
44
|
+
|
|
45
|
+
def self.run_cli: (Array[String] args) -> Integer
|
|
46
|
+
|
|
47
|
+
# The extension: positional arguments, in the order of the keywords above.
|
|
48
|
+
module Native
|
|
49
|
+
VERSION: String
|
|
50
|
+
|
|
51
|
+
def self.cutter_sanborn: (String?, String?, String?, Integer?, Integer?) -> String
|
|
52
|
+
def self.cutter_sanborn_detailed: (String?, String?, String?, Integer?, Integer?) -> sanborn_parts
|
|
53
|
+
def self.cutter_loc: (String?, String?, String?, Integer?) -> String
|
|
54
|
+
def self.cutter_loc_detailed: (String?, String?, String?, Integer?) -> loc_parts
|
|
55
|
+
def self.run_cli: (Array[String]) -> Integer
|
|
56
|
+
end
|
|
4
57
|
end
|
metadata
CHANGED
|
@@ -1,31 +1,61 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: csan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- VERALVX
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rb_sys
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.9.130
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.9.130
|
|
26
|
+
description: |
|
|
27
|
+
Generate Cutter-Sanborn three-figure and Library of Congress cutter
|
|
28
|
+
numbers from author names and titles, with the csan command-line tool.
|
|
29
|
+
The numbers come from the csan Rust library (a native extension), so they
|
|
30
|
+
match the csan CLI and its Python, JavaScript, and Typst packages.
|
|
12
31
|
email:
|
|
13
32
|
- veralvx@veralvx.com
|
|
14
|
-
executables:
|
|
15
|
-
|
|
33
|
+
executables:
|
|
34
|
+
- csan
|
|
35
|
+
extensions:
|
|
36
|
+
- ext/csan/extconf.rb
|
|
16
37
|
extra_rdoc_files: []
|
|
17
38
|
files:
|
|
18
|
-
- LICENSE
|
|
39
|
+
- LICENSE-APACHE
|
|
40
|
+
- LICENSE-MIT
|
|
19
41
|
- README.md
|
|
20
|
-
-
|
|
42
|
+
- exe/csan
|
|
43
|
+
- ext/csan/Cargo.lock
|
|
44
|
+
- ext/csan/Cargo.toml
|
|
45
|
+
- ext/csan/extconf.rb
|
|
46
|
+
- ext/csan/src/lib.rs
|
|
21
47
|
- lib/csan.rb
|
|
22
48
|
- lib/csan/version.rb
|
|
23
49
|
- sig/csan.rbs
|
|
24
50
|
homepage: https://github.com/veralvx/csan
|
|
25
51
|
licenses:
|
|
26
52
|
- MIT
|
|
53
|
+
- Apache-2.0
|
|
27
54
|
metadata:
|
|
28
55
|
homepage_uri: https://github.com/veralvx/csan
|
|
56
|
+
source_code_uri: https://github.com/veralvx/csan/tree/main/crates/csan-rb
|
|
57
|
+
changelog_uri: https://github.com/veralvx/csan/blob/main/CHANGELOG.md
|
|
58
|
+
bug_tracker_uri: https://github.com/veralvx/csan/issues
|
|
29
59
|
rdoc_options: []
|
|
30
60
|
require_paths:
|
|
31
61
|
- lib
|
|
@@ -33,14 +63,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
33
63
|
requirements:
|
|
34
64
|
- - ">="
|
|
35
65
|
- !ruby/object:Gem::Version
|
|
36
|
-
version: 3.
|
|
66
|
+
version: '3.3'
|
|
37
67
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
68
|
requirements:
|
|
39
69
|
- - ">="
|
|
40
70
|
- !ruby/object:Gem::Version
|
|
41
71
|
version: '0'
|
|
42
72
|
requirements: []
|
|
43
|
-
rubygems_version: 3.
|
|
73
|
+
rubygems_version: 3.6.9
|
|
44
74
|
specification_version: 4
|
|
45
|
-
summary:
|
|
75
|
+
summary: Cutter-Sanborn and Library of Congress cutter numbers for library cataloging
|
|
46
76
|
test_files: []
|
data/LICENSE.txt
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 VERALVX
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
THE SOFTWARE.
|
data/Rakefile
DELETED