lc_callnumber 0.0.1 → 0.1.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/README.md +1 -1
- data/lib/lc_callnumber/lcc.rb +137 -60
- data/lib/lc_callnumber/version.rb +1 -1
- data/test/test_lc_callnumber.rb +25 -5
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d173a4ec0a1b4ba26c0beec642a3f66b845c67d4
|
4
|
+
data.tar.gz: 41a42a08153cb57f4f0c775794cbf72c56890369
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3aa66113e6ac592735e4bfdba100b7b174635054dc26b0d136d749329b48df6957233bef6d426e50d07ab794a0e010b23cdcbd6f04f1ce52703d28ad9b53fe1e
|
7
|
+
data.tar.gz: 686dc9b45975782c5c426af6430e0bec8d08c21fa41a6aff5dd4b7b768a2871fd51d8bfb48f1a43bb8fe0b1c36e286db1319131c35f272f586d8b96c2899cf43
|
data/README.md
CHANGED
@@ -32,7 +32,7 @@ For purposes of this class, an LC Call Number consists of the following parts. O
|
|
32
32
|
|
33
33
|
* __Letter(s).__ One or more letters
|
34
34
|
* __Digit(s).__ One or more digits, optionally with a decimal point.
|
35
|
-
* __Doon1 (_Date Or Other Number_)__. Relatively rare as these things go, a DOON is used to represent the date the work is _about_ (as opposed to, say, the year it was published) or, in some cases, an identifier for an army division or group (say, "12th" for the US Army Twelfth Infantry).
|
35
|
+
* __Doon1 (_Date Or Other Number_)__. [Note: no one but me calls it a 'doon', but I needed a term and there wasn't another]. Relatively rare as these things go, a DOON is used to represent the date the work is _about_ (as opposed to, say, the year it was published) or, in some cases, an identifier for an army division or group (say, "12th" for the US Army Twelfth Infantry).
|
36
36
|
* __First cutter__. The first "Cutter Number" consisting of a letter followed by one or more digits. The first cutter is always supposed to be preceded by a dot, but, you know, isn't always.
|
37
37
|
* __Doon2__. Another date or other number
|
38
38
|
* __"Extra" Cutters__. The 2nd through Nth Cutter numbers, lumped together because we don't have to worry about them getting interspersed with doons.
|
data/lib/lc_callnumber/lcc.rb
CHANGED
@@ -4,7 +4,7 @@ module LCCallNumber
|
|
4
4
|
|
5
5
|
@parser = Parser.new
|
6
6
|
@transformer = Transform.new
|
7
|
-
|
7
|
+
|
8
8
|
def self.parse(i)
|
9
9
|
return UnparseableCallNumber.new(i) if i.nil?
|
10
10
|
i.chomp!
|
@@ -13,13 +13,13 @@ module LCCallNumber
|
|
13
13
|
i.gsub!(/\[(.+)\]/, "$1")
|
14
14
|
i.gsub!('\\', ' ')
|
15
15
|
|
16
|
-
# We also have a bunch that start with a slash and have slashes where you'd
|
16
|
+
# We also have a bunch that start with a slash and have slashes where you'd
|
17
17
|
# expect logical breaks. Don't know why.
|
18
|
-
|
18
|
+
|
19
19
|
if i =~ /\A\//
|
20
20
|
i.gsub!('/', ' ')
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
# Ditch leading + or *
|
24
24
|
i.gsub!(/\A[+*]/, '')
|
25
25
|
|
@@ -35,7 +35,7 @@ module LCCallNumber
|
|
35
35
|
!(/\d/.match i)
|
36
36
|
return UnparseableCallNumber.new(orig)
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
begin
|
40
40
|
p = @parser.parse(i)
|
41
41
|
# puts p
|
@@ -46,12 +46,12 @@ module LCCallNumber
|
|
46
46
|
return UnparseableCallNumber.new(orig)
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
55
|
|
56
56
|
# The "lead" -- initial letter(s) and digit(s)
|
57
57
|
class Lead
|
@@ -64,28 +64,28 @@ module LCCallNumber
|
|
64
64
|
"Lead<#{letters}, #{digits.inspect}>"
|
65
65
|
end
|
66
66
|
end
|
67
|
-
|
68
|
-
# A generic "Decimal" part, where we keep the
|
67
|
+
|
68
|
+
# A generic "Decimal" part, where we keep the
|
69
69
|
# integer and fractional parts separate so
|
70
70
|
# we can construct a string more easily
|
71
|
-
|
71
|
+
|
72
72
|
class Decimal
|
73
73
|
attr_accessor :intpart, :fractpart
|
74
74
|
def initialize(i, f=nil)
|
75
75
|
@intpart = i
|
76
76
|
@fractpart = f
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
79
|
def to_num
|
80
80
|
fractpart ? "#{intpart}.#{fractpart}".to_f : intpart.to_i
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
def to_s
|
84
84
|
fractpart ? "#{intpart}.#{fractpart}" : intpart
|
85
85
|
end
|
86
|
-
|
87
|
-
|
88
|
-
def inspect
|
86
|
+
|
87
|
+
|
88
|
+
def inspect
|
89
89
|
if fractpart
|
90
90
|
"#{intpart}:#{fractpart}"
|
91
91
|
else
|
@@ -93,36 +93,36 @@ module LCCallNumber
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
# A "Cutter" is a single letter followed by one or more digits
|
98
98
|
# It is sometimes preceded by a decimal point. We note whether or not
|
99
99
|
# we got a decimal point because it may be a hint as to whether or not
|
100
100
|
# we're actually looking at a Cutter.
|
101
|
-
|
101
|
+
|
102
102
|
class Cutter
|
103
103
|
attr_accessor :letter, :digits, :dot
|
104
104
|
def initialize(l,d, dot=nil)
|
105
|
-
@letter = l
|
106
|
-
@digits = d
|
105
|
+
@letter = l.to_s
|
106
|
+
@digits = d.to_i
|
107
107
|
@dot = !dot.nil?
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
def inspect
|
111
111
|
"Cut<#{letter},#{digits}>"
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
def to_s
|
115
115
|
"#{letter}#{digits}"
|
116
116
|
end
|
117
117
|
end
|
118
|
-
|
119
|
-
|
118
|
+
|
119
|
+
|
120
120
|
# Some call numbers have a year, infantry division, etc. preceeding
|
121
121
|
# and/or following the first cutter. We take all three as a unit so
|
122
122
|
# we can deal with edge cases more easily.
|
123
123
|
#
|
124
124
|
# "doon" in this case is "Date Or Other Number"
|
125
|
-
|
125
|
+
|
126
126
|
class FirstCutterSet
|
127
127
|
attr_accessor :doon1, :cutter, :doon2
|
128
128
|
def initialize(d1, cutter, d2)
|
@@ -131,84 +131,161 @@ module LCCallNumber
|
|
131
131
|
@cutter = cutter
|
132
132
|
end
|
133
133
|
end
|
134
|
-
|
134
|
+
|
135
135
|
# Bring it all together in the callnumber
|
136
|
-
# We feed it the parts we have, and then reassign fields based on
|
136
|
+
# We feed it the parts we have, and then reassign fields based on
|
137
137
|
# what we have.
|
138
138
|
#
|
139
139
|
# To wit:
|
140
140
|
# Q123 .C4 1990 --- published in 1990
|
141
141
|
# Q123 .C4 1990 .D5 2003 -- published in 2003 with a 2nd doon of 1990
|
142
|
-
|
143
|
-
|
142
|
+
|
143
|
+
|
144
144
|
class CallNumber
|
145
|
-
|
146
|
-
|
145
|
+
|
146
|
+
include Comparable
|
147
|
+
attr_accessor :letters, :digits, :doon1, :firstcutter, :doon2,
|
147
148
|
:extra_cutters, :year, :rest
|
148
149
|
attr_accessor :original_string
|
149
|
-
|
150
|
-
|
150
|
+
|
151
|
+
|
151
152
|
def initialize(lead, fcset=nil, ec=[], year=nil, rest=nil)
|
152
|
-
@letters = lead.letters
|
153
|
+
@letters = lead.letters.to_s.upcase
|
153
154
|
@digits = lead.digits.to_num
|
154
|
-
|
155
|
+
|
155
156
|
if fcset
|
156
157
|
@doon1 = fcset.doon1
|
157
158
|
@doon2 = fcset.doon2
|
158
159
|
@firstcutter = fcset.cutter
|
159
160
|
end
|
160
|
-
|
161
|
+
|
161
162
|
@extra_cutters = ec
|
162
163
|
@year = year
|
163
164
|
@rest = rest
|
164
|
-
|
165
|
+
|
165
166
|
@rest.strip! if @rest
|
166
|
-
|
167
|
+
|
167
168
|
self.reassign_fields
|
168
169
|
end
|
169
|
-
|
170
|
+
|
170
171
|
def reassign_fields
|
171
172
|
# If we've got a doon2, but no year and no extra cutters,
|
172
173
|
# put the doon2 in the year
|
173
|
-
|
174
|
+
|
174
175
|
if doon2 and (doon2=~ /\A\d+\Z/) and extra_cutters.empty? and year.nil?
|
175
176
|
@year = @doon2
|
176
177
|
@doon2 = nil
|
177
178
|
end
|
178
179
|
end
|
179
|
-
|
180
|
+
|
180
181
|
def inspect
|
181
182
|
header = %w(letters digits doon1 cut1 doon2 othercut)
|
182
183
|
actual = [letters,digits,doon1,firstcutter,doon2,extra_cutters.join(','),year,rest].map{|x| x.to_s}
|
183
184
|
fmt = '%-7s %-9s %-6s %-6s %-6s %-20s year=%-6s rest=%-s'
|
184
185
|
[('%-7s %-9s %-6s %-6s %-6s %-s' % header), (fmt % actual)].join("\n")
|
185
|
-
|
186
|
+
|
186
187
|
end
|
187
|
-
|
188
|
-
|
189
|
-
def valid?
|
188
|
+
|
189
|
+
|
190
|
+
def valid?
|
190
191
|
@letters && @digits
|
191
192
|
end
|
192
|
-
|
193
|
+
|
194
|
+
def <=>(other)
|
195
|
+
# Make call numbers sortable.
|
196
|
+
# Work through the various parts, from left to right.
|
197
|
+
# (They compare as strings or numbers as appropriate, thanks to the above.)
|
198
|
+
|
199
|
+
letters_cmp = letters <=> other.letters
|
200
|
+
return letters_cmp unless letters_cmp == 0
|
201
|
+
|
202
|
+
digits_cmp = digits <=> other.digits
|
203
|
+
return digits_cmp unless digits_cmp == 0
|
204
|
+
|
205
|
+
doon1_cmp = doon1 <=> other.doon1
|
206
|
+
return doon1_cmp unless doon1_cmp == 0
|
207
|
+
|
208
|
+
# Cutters have letters and numbers, which we need to compare separately.
|
209
|
+
if firstcutter
|
210
|
+
if other.firstcutter
|
211
|
+
# Both have firstcutters! Hurrah!
|
212
|
+
# Compare the letter
|
213
|
+
firstcutter_cmp = firstcutter.letter <=> other.firstcutter.letter
|
214
|
+
# If that didn't help, compare the digits
|
215
|
+
if firstcutter_cmp == 0
|
216
|
+
firstcutter_cmp = firstcutter.digits <=> other.firstcutter.digits
|
217
|
+
end
|
218
|
+
# If that didn't help, if other has an extra_cutter, it is last
|
219
|
+
if firstcutter_cmp == 0
|
220
|
+
firstcutter_cmp = -1 if (extra_cutters.empty? && ! other.extra_cutters.empty?)
|
221
|
+
end
|
222
|
+
else
|
223
|
+
# self has a firstcutter but other doesn't, so other is first
|
224
|
+
firstcutter_cmp = 1
|
225
|
+
end
|
226
|
+
else
|
227
|
+
# self has no firstcutter
|
228
|
+
if other.firstcutter
|
229
|
+
# other does, so it comes last
|
230
|
+
firstcutter_cmp = -1
|
231
|
+
else
|
232
|
+
# Neither has one, so carry on
|
233
|
+
firstcutter_cmp = 0
|
234
|
+
end
|
235
|
+
end
|
236
|
+
return firstcutter_cmp unless firstcutter_cmp == 0
|
237
|
+
|
238
|
+
doon2_cmp = doon2 <=> other.doon2
|
239
|
+
return doon2_cmp unless doon2_cmp == 0
|
240
|
+
|
241
|
+
# The extra_cutters are an array, so we need to compare each.
|
242
|
+
# Surely there is an easier way to do this?
|
243
|
+
extra_cutters_cmp = 0
|
244
|
+
# STDERR.puts extra_cutters
|
245
|
+
extra_cutters.each_index do |i|
|
246
|
+
if extra_cutters_cmp == 0 # Stop when we need go no further
|
247
|
+
# First, compare the letter
|
248
|
+
if other.extra_cutters[i]
|
249
|
+
extra_cutters_cmp = extra_cutters[i].letter <=> other.extra_cutters[i].letter
|
250
|
+
# If that didn't help, compare the digits
|
251
|
+
if extra_cutters_cmp == 0
|
252
|
+
extra_cutters_cmp = extra_cutters[i].digits <=> other.extra_cutters[i].digits
|
253
|
+
end
|
254
|
+
# If that didn't help, if self has no more extra_cutters but other does, self is first
|
255
|
+
if extra_cutters_cmp == 0
|
256
|
+
extra_cutters_cmp = -1 if (extra_cutters[i+1].nil? && other.extra_cutters[i+1])
|
257
|
+
end
|
258
|
+
else
|
259
|
+
# other has no extra_cutter, so it is first
|
260
|
+
extra_cutters_cmp = 1
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
return extra_cutters_cmp unless extra_cutters_cmp == 0
|
265
|
+
|
266
|
+
year_cmp = year <=> other.year
|
267
|
+
return year_cmp unless year_cmp == 0
|
268
|
+
|
269
|
+
rest_cmp = rest <=> other.rest
|
270
|
+
return rest_cmp
|
271
|
+
|
272
|
+
end
|
273
|
+
|
193
274
|
end
|
194
|
-
|
275
|
+
|
195
276
|
class UnparseableCallNumber < CallNumber
|
196
277
|
|
197
278
|
def initialize(str)
|
198
279
|
self.original_string = str
|
199
280
|
self.extra_cutters = []
|
200
281
|
end
|
201
|
-
|
202
|
-
def valid?
|
282
|
+
|
283
|
+
def valid?
|
203
284
|
false
|
204
285
|
end
|
205
|
-
|
286
|
+
|
206
287
|
end
|
207
|
-
|
208
|
-
|
209
|
-
|
288
|
+
|
289
|
+
|
290
|
+
|
210
291
|
end
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
data/test/test_lc_callnumber.rb
CHANGED
@@ -22,11 +22,31 @@ describe "Basics" do
|
|
22
22
|
assert_equal other_cutters.to_s.split(/\s*,\s*/), lc.extra_cutters.map(&:to_s), "#{orig} -> extra_cutters #{lc.inspect}"
|
23
23
|
assert_equal pubyear.to_s, lc.year.to_s, "#{orig} -> year #{lc.inspect}"
|
24
24
|
assert_equal rest.to_s, lc.rest.to_s, "#{orig} -> rest #{lc.inspect}"
|
25
|
-
|
25
|
+
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "Sorting" do
|
32
|
+
it "sorts call numbers properly" do
|
33
|
+
a1 = LCCallNumber.parse("A 50")
|
34
|
+
a2 = LCCallNumber.parse("A 7")
|
35
|
+
q1 = LCCallNumber.parse("QA 500")
|
36
|
+
q2 = LCCallNumber.parse("QA 500.M500")
|
37
|
+
q3 = LCCallNumber.parse("QA 500.M500 T59")
|
38
|
+
q4 = LCCallNumber.parse("QA 500.M500 T60")
|
39
|
+
q5 = LCCallNumber.parse("QA 500.M500 T60 A1")
|
40
|
+
q6 = LCCallNumber.parse("QA 500.M500 T60 Z54")
|
41
|
+
assert (a1 <=> a1) == 0 # a1 is a1
|
42
|
+
assert (a1 <=> a2) == 1 # a1 > a2
|
43
|
+
assert (a2 <=> q1) == -1 # a2 < q1
|
44
|
+
assert (q1 <=> q2) == -1 # q1 < q2
|
45
|
+
assert (q2 <=> q3) == -1 # q2 < q3
|
46
|
+
assert (q3 <=> q3) == 0 # q3 is q3
|
47
|
+
assert (q3 <=> q4) == -1 # q3 < q4
|
48
|
+
assert (q4 <=> q5) == -1 # q4 < q5
|
49
|
+
assert (q5 <=> q6) == -1 # q5 < q6
|
50
|
+
# Is there some way to put some test numbers into an array, sort it, and compare the original and sorted arrays?
|
28
51
|
end
|
29
52
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lc_callnumber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bill Dueber
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parslet
|
@@ -107,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
107
|
version: '0'
|
108
108
|
requirements: []
|
109
109
|
rubyforge_project:
|
110
|
-
rubygems_version: 2.
|
110
|
+
rubygems_version: 2.4.5
|
111
111
|
signing_key:
|
112
112
|
specification_version: 4
|
113
113
|
summary: Work with LC Call (Classification) Numbers, including an attempt to parse
|
@@ -116,3 +116,4 @@ test_files:
|
|
116
116
|
- test/minitest_helper.rb
|
117
117
|
- test/test_data/basics.txt
|
118
118
|
- test/test_lc_callnumber.rb
|
119
|
+
has_rdoc:
|