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 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,10 @@
1
+ from .cutter import cutter_identifier, cutter_number
2
+ from .naming import ascii_name, compose_name, process_name
3
+
4
+ __all__ = [
5
+ "ascii_name",
6
+ "compose_name",
7
+ "cutter_identifier",
8
+ "cutter_number",
9
+ "process_name",
10
+ ]
@@ -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