csan 0.1.0__tar.gz
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.
- csan-0.1.0/PKG-INFO +80 -0
- csan-0.1.0/README.md +40 -0
- csan-0.1.0/pyproject.toml +130 -0
- csan-0.1.0/src/csan/__init__.py +10 -0
- csan-0.1.0/src/csan/cutter.py +141 -0
- csan-0.1.0/src/csan/data.py +12331 -0
- csan-0.1.0/src/csan/main.py +72 -0
- csan-0.1.0/src/csan/naming.py +93 -0
- csan-0.1.0/src/csan/py.typed +0 -0
csan-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: csan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool that generates a Cutter-Sanborn identifier, given a first and a last name.
|
|
5
|
+
Keywords: cutter,sanborn,cutter-sanborn,cutter-number,cutter-notation,catalog,cataloging,cataloguing,library,library-catalog,call-number,three-figure,bibliographic,cli,command-line
|
|
6
|
+
Author: VERA LVX
|
|
7
|
+
Author-email: VERA LVX <veralvx@veralvx.com>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Text Processing
|
|
26
|
+
Classifier: Topic :: Text Processing :: General
|
|
27
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
28
|
+
Classifier: Topic :: Utilities
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Maintainer: VERA LVX
|
|
31
|
+
Maintainer-email: VERA LVX <veralvx@veralvx.com>
|
|
32
|
+
Requires-Python: >=3.10
|
|
33
|
+
Project-URL: Changelog, https://github.com/veralvx/csan/blob/main/CHANGELOG.md
|
|
34
|
+
Project-URL: Documentation, https://github.com/veralvx/csan
|
|
35
|
+
Project-URL: Homepage, https://github.com/veralvx/csan
|
|
36
|
+
Project-URL: Issues, https://github.com/veralvx/csan/issues
|
|
37
|
+
Project-URL: Releasenotes, https://github.com/veralvx/csan/releases
|
|
38
|
+
Project-URL: Repository, https://github.com/veralvx/csan
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# CSAN: Cutter-Sanborn Number Generator
|
|
42
|
+
|
|
43
|
+
`csan` is a CLI tool that generates a Cutter-Sanborn identifier, given a first and a last name.
|
|
44
|
+
|
|
45
|
+
The Cutter-Sanborn identifier, commonly called "Cutter number", is an alphanumeric code used in library classification systems in order to arrange books alphabetically by author. It consists of the first letter of the author's last name followed by three-digit number derived from a predefined [table](https://github.com/veralvx/cutter-sanborn-table). This system was originally developed by Charles Cutter, and revised by Kate Sanborn.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
You can install this tool using `pip` or `uv`:
|
|
50
|
+
|
|
51
|
+
```console
|
|
52
|
+
uv tool install csan
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```console
|
|
56
|
+
pip install csan
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
usage: csan [-h] -f FIRST_NAME -l LAST_NAME [-t TITLE] [-v]
|
|
64
|
+
|
|
65
|
+
Cutter-Sanborn identifier generator.
|
|
66
|
+
|
|
67
|
+
options:
|
|
68
|
+
-h, --help show this help message and exit
|
|
69
|
+
-f FIRST_NAME, --first-name FIRST_NAME
|
|
70
|
+
-l LAST_NAME, --last-name LAST_NAME
|
|
71
|
+
-t TITLE, --title TITLE
|
|
72
|
+
-v, --verbose
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Examples
|
|
76
|
+
|
|
77
|
+
- `csan -f John -l Doe` -> D649
|
|
78
|
+
- `csan -f John -l Doe -t "My Book"` -> D649m
|
|
79
|
+
- `csan -f First -l Last -v` -> L349, with log output to the console
|
|
80
|
+
- `csan -f Jorge -l "De la Cruz"` -> D332
|
csan-0.1.0/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# CSAN: Cutter-Sanborn Number Generator
|
|
2
|
+
|
|
3
|
+
`csan` is a CLI tool that generates a Cutter-Sanborn identifier, given a first and a last name.
|
|
4
|
+
|
|
5
|
+
The Cutter-Sanborn identifier, commonly called "Cutter number", is an alphanumeric code used in library classification systems in order to arrange books alphabetically by author. It consists of the first letter of the author's last name followed by three-digit number derived from a predefined [table](https://github.com/veralvx/cutter-sanborn-table). This system was originally developed by Charles Cutter, and revised by Kate Sanborn.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
You can install this tool using `pip` or `uv`:
|
|
10
|
+
|
|
11
|
+
```console
|
|
12
|
+
uv tool install csan
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```console
|
|
16
|
+
pip install csan
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
```console
|
|
23
|
+
usage: csan [-h] -f FIRST_NAME -l LAST_NAME [-t TITLE] [-v]
|
|
24
|
+
|
|
25
|
+
Cutter-Sanborn identifier generator.
|
|
26
|
+
|
|
27
|
+
options:
|
|
28
|
+
-h, --help show this help message and exit
|
|
29
|
+
-f FIRST_NAME, --first-name FIRST_NAME
|
|
30
|
+
-l LAST_NAME, --last-name LAST_NAME
|
|
31
|
+
-t TITLE, --title TITLE
|
|
32
|
+
-v, --verbose
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
- `csan -f John -l Doe` -> D649
|
|
38
|
+
- `csan -f John -l Doe -t "My Book"` -> D649m
|
|
39
|
+
- `csan -f First -l Last -v` -> L349, with log output to the console
|
|
40
|
+
- `csan -f Jorge -l "De la Cruz"` -> D332
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "csan"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CLI tool that generates a Cutter-Sanborn identifier, given a first and a last name."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "VERA LVX", email = "veralvx@veralvx.com" }]
|
|
7
|
+
maintainers = [{ name = "VERA LVX", email = "veralvx@veralvx.com" }]
|
|
8
|
+
license = "Apache-2.0"
|
|
9
|
+
keywords = [
|
|
10
|
+
"cutter",
|
|
11
|
+
"sanborn",
|
|
12
|
+
"cutter-sanborn",
|
|
13
|
+
"cutter-number",
|
|
14
|
+
"cutter-notation",
|
|
15
|
+
"catalog",
|
|
16
|
+
"cataloging",
|
|
17
|
+
"cataloguing",
|
|
18
|
+
"library",
|
|
19
|
+
"library-catalog",
|
|
20
|
+
"call-number",
|
|
21
|
+
"three-figure",
|
|
22
|
+
"bibliographic",
|
|
23
|
+
"cli",
|
|
24
|
+
"command-line"
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.10"
|
|
27
|
+
# https://pypi.org/classifiers/
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Environment :: Console",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
"Intended Audience :: Information Technology",
|
|
33
|
+
"License :: OSI Approved :: Apache Software License",
|
|
34
|
+
"Natural Language :: English",
|
|
35
|
+
"Operating System :: OS Independent",
|
|
36
|
+
"Programming Language :: Python :: 3",
|
|
37
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
38
|
+
"Programming Language :: Python :: 3.10",
|
|
39
|
+
"Programming Language :: Python :: 3.11",
|
|
40
|
+
"Programming Language :: Python :: 3.12",
|
|
41
|
+
"Programming Language :: Python :: 3.13",
|
|
42
|
+
"Programming Language :: Python :: 3.14",
|
|
43
|
+
"Topic :: Software Development :: Libraries",
|
|
44
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
45
|
+
"Topic :: Text Processing",
|
|
46
|
+
"Topic :: Text Processing :: General",
|
|
47
|
+
"Topic :: Text Processing :: Indexing",
|
|
48
|
+
"Topic :: Utilities",
|
|
49
|
+
"Typing :: Typed"
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Changelog = "https://github.com/veralvx/csan/blob/main/CHANGELOG.md"
|
|
54
|
+
Documentation = "https://github.com/veralvx/csan"
|
|
55
|
+
Homepage = "https://github.com/veralvx/csan"
|
|
56
|
+
Issues = "https://github.com/veralvx/csan/issues"
|
|
57
|
+
Releasenotes = "https://github.com/veralvx/csan/releases"
|
|
58
|
+
Repository = "https://github.com/veralvx/csan"
|
|
59
|
+
|
|
60
|
+
[project.scripts]
|
|
61
|
+
csan = "csan.main:main"
|
|
62
|
+
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["uv_build>=0.9.7,<0.10.0"]
|
|
65
|
+
build-backend = "uv_build"
|
|
66
|
+
|
|
67
|
+
[tool.ruff.format]
|
|
68
|
+
docstring-code-format = true
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
preview = false
|
|
72
|
+
select = ["ALL"]
|
|
73
|
+
ignore = [
|
|
74
|
+
"ANN001",
|
|
75
|
+
"ANN002",
|
|
76
|
+
"ANN201",
|
|
77
|
+
"ANN202",
|
|
78
|
+
"ANN204",
|
|
79
|
+
"ANN206",
|
|
80
|
+
"ANN401",
|
|
81
|
+
"COM812",
|
|
82
|
+
"CPY001",
|
|
83
|
+
"D100",
|
|
84
|
+
"D101",
|
|
85
|
+
"D102",
|
|
86
|
+
"D103",
|
|
87
|
+
"D104",
|
|
88
|
+
"D105",
|
|
89
|
+
"D107",
|
|
90
|
+
"D203",
|
|
91
|
+
"D212",
|
|
92
|
+
"D413",
|
|
93
|
+
"E731",
|
|
94
|
+
"EM101",
|
|
95
|
+
"EM102",
|
|
96
|
+
"ERA001", #commented-out-code (ERA001)
|
|
97
|
+
"FIX002",
|
|
98
|
+
"FURB118",
|
|
99
|
+
"INP001",
|
|
100
|
+
"PLR2004",
|
|
101
|
+
"PLR0904", # Too many public methods (>20)
|
|
102
|
+
"PLR1702", # too-many-nested-blocks (PLR1702)
|
|
103
|
+
"PT011",
|
|
104
|
+
"PTH123", # use pathlib instead of open
|
|
105
|
+
"PYI056",
|
|
106
|
+
"S101",
|
|
107
|
+
"S404", # suspicious-subprocess-import
|
|
108
|
+
"S603", # `subprocess` call: check for execution of untrusted inputRuff
|
|
109
|
+
"S607", # start-process-with-partial-path (S607)
|
|
110
|
+
"SIM105", # suppressible-exception (SIM105)
|
|
111
|
+
"SLF001", # private-member-access
|
|
112
|
+
"T201",
|
|
113
|
+
"TC006", #runtime-cast-value
|
|
114
|
+
"TD",
|
|
115
|
+
"TRY003",
|
|
116
|
+
"UP030" #format-literals (UP030)
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
[tool.pyright]
|
|
120
|
+
typeCheckingMode = "standard"
|
|
121
|
+
|
|
122
|
+
[dependency-groups]
|
|
123
|
+
dev = [
|
|
124
|
+
"basedpyright>=1.33.0",
|
|
125
|
+
"mypy>=1.18.2",
|
|
126
|
+
"pyrefly>=0.40.0",
|
|
127
|
+
"pytest>=8.4.2",
|
|
128
|
+
"ruff>=0.14.4",
|
|
129
|
+
"ty>=0.0.1a25"
|
|
130
|
+
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from .data import CUTTER_DATA
|
|
4
|
+
from .naming import compose_name, process_name
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def cutter_number(
|
|
10
|
+
first_name: str,
|
|
11
|
+
last_name: str,
|
|
12
|
+
composed_name: str | None = None,
|
|
13
|
+
composed_name_abbr: str | None = None,
|
|
14
|
+
*,
|
|
15
|
+
verbose: bool = False,
|
|
16
|
+
) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Generate/Retrieve a cutter-sanborn number, given a first and last name.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
first_name (str): a person's first name (e.g. "Jane");
|
|
22
|
+
last_name (str): a person's last name (e.g. "Doe");
|
|
23
|
+
composed_name (str|None): a composition in the format "Last, First";
|
|
24
|
+
composed_name_abbr (str|None): an abbreviated compositon, format "Last, F."
|
|
25
|
+
verbose (bool): show debug logs.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
An integer, retrieved from the Cutter-Sanborn table.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ValueError: if no Cutter-Sanborn number is found.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
>>> cutter_number("First", "Last")
|
|
35
|
+
349
|
|
36
|
+
"""
|
|
37
|
+
log_level = logging.DEBUG if verbose else logging.INFO
|
|
38
|
+
logging.basicConfig(level=log_level, format="%(message)s")
|
|
39
|
+
|
|
40
|
+
first_name, last_name = process_name(first_name, last_name)
|
|
41
|
+
|
|
42
|
+
if (composed_name is None) or (composed_name_abbr is None):
|
|
43
|
+
composed_name, composed_name_abbr = compose_name(first_name, last_name)
|
|
44
|
+
|
|
45
|
+
cutter_s = None
|
|
46
|
+
|
|
47
|
+
obvious_attempts = [
|
|
48
|
+
composed_name,
|
|
49
|
+
last_name + f", {first_name[:3]}.",
|
|
50
|
+
composed_name_abbr,
|
|
51
|
+
last_name,
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
if attempt := next((a for a in obvious_attempts if a in CUTTER_DATA), None):
|
|
55
|
+
logger.debug(
|
|
56
|
+
"Obvious attempts: %s\nReturning match '%s' from obvious attempts",
|
|
57
|
+
obvious_attempts,
|
|
58
|
+
attempt,
|
|
59
|
+
)
|
|
60
|
+
return CUTTER_DATA[attempt]
|
|
61
|
+
|
|
62
|
+
sieved_data = [k for k in CUTTER_DATA if k.startswith(last_name[0:2])]
|
|
63
|
+
|
|
64
|
+
composed_name_decrescent = [
|
|
65
|
+
composed_name[: i + 1]
|
|
66
|
+
for i in range(len(composed_name), 0, -1)
|
|
67
|
+
if not composed_name[: i + 1].endswith((",", " "))
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
logger.debug("\nDecrescent last name list: %s", composed_name_decrescent)
|
|
71
|
+
|
|
72
|
+
for pos, name in enumerate(composed_name_decrescent[1:]):
|
|
73
|
+
sub_sieved = [
|
|
74
|
+
k
|
|
75
|
+
for k in sieved_data
|
|
76
|
+
if (k.startswith(name) and (len(k) <= len(composed_name)))
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
if not sub_sieved:
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
miss_lttrs = list(composed_name_decrescent[0][len(name) :])
|
|
83
|
+
|
|
84
|
+
logger.debug("Name: %s\nPos: %s\nSieved List: %s", name, pos, sub_sieved)
|
|
85
|
+
|
|
86
|
+
for candidate in sub_sieved:
|
|
87
|
+
logger.debug("Candidate: %s", candidate)
|
|
88
|
+
|
|
89
|
+
if candidate == name:
|
|
90
|
+
cutter_s = candidate
|
|
91
|
+
else:
|
|
92
|
+
xtra_lttrs = list(candidate[len(name) :].replace(".", ""))
|
|
93
|
+
logger.debug("Missing Letters: %s", miss_lttrs)
|
|
94
|
+
logger.debug("Extra Letters: %s", xtra_lttrs)
|
|
95
|
+
|
|
96
|
+
if not (pairs := list(zip(miss_lttrs, xtra_lttrs, strict=False))):
|
|
97
|
+
logger.debug("Pairs are empty. Continuing...")
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
logger.debug("Pairs: %s\nMatch: %s.", pairs, cutter_s)
|
|
101
|
+
|
|
102
|
+
cutter_s = (
|
|
103
|
+
candidate
|
|
104
|
+
if all(x >= y and (x.isalpha() == y.isalpha()) for x, y in pairs)
|
|
105
|
+
else cutter_s
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
logger.debug("Match: %s\n", cutter_s)
|
|
109
|
+
|
|
110
|
+
if cutter_s is not None:
|
|
111
|
+
return CUTTER_DATA[cutter_s]
|
|
112
|
+
|
|
113
|
+
logger.debug("\n")
|
|
114
|
+
|
|
115
|
+
if cutter_s is None:
|
|
116
|
+
raise RuntimeError("Unable to retrieve Cutter-Sanborn number.")
|
|
117
|
+
|
|
118
|
+
return 0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# first_letters = last_name[0] if last_name[1] in "aeiou" else last_name[0:2].upper()
|
|
122
|
+
def cutter_identifier(
|
|
123
|
+
last_name: str, cutter_number: int, title: str | None = None
|
|
124
|
+
) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Generate an identifier based on last name, cutter-sanborn number and a title.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
last_name (str): a person's last name;
|
|
130
|
+
cutter_number (int): the cutter-sanborn number;
|
|
131
|
+
title (str): a work's title.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A string identifier
|
|
135
|
+
|
|
136
|
+
Examples:
|
|
137
|
+
>>> cutter_identifier("Doe", 649, "Title")
|
|
138
|
+
'D649t'
|
|
139
|
+
"""
|
|
140
|
+
title = "" if title is None else title[0].lower()
|
|
141
|
+
return last_name[0] + str(cutter_number) + title
|