lc_callnumber 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/Rakefile +8 -0
- data/lc_callnumber.gemspec +26 -0
- data/lib/lc_callnumber/lcc.rb +214 -0
- data/lib/lc_callnumber/parser.rb +113 -0
- data/lib/lc_callnumber/version.rb +3 -0
- data/lib/lc_callnumber.rb +4 -0
- data/test/minitest_helper.rb +13 -0
- data/test/test_data/basics.txt +26 -0
- data/test/test_lc_callnumber.rb +32 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 080e05e9e7c6084e500e3fb0ca2d827c4dc52474
|
4
|
+
data.tar.gz: 222d0cce1e42df11b29496b191da1ed60ae37e42
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3291b552acdb8d6339d0043ac0cbe66e0e906f4b007c3e04a4dcc972a4947e299930bb1c66bbb999a6bb8510d0b55ec6b2611e59dc9aafe7f76077c70bcd76c5
|
7
|
+
data.tar.gz: 8d5840f2a39cbcee9aedb56135b7b556c2eb046615224331c4c40ff97bdbb59f08602154f2e5d9ff22831c03620d2ef95511d0cd6d182ec04eb81fd7229e425c
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Bill Dueber
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
[](http://travis-ci.org/billdueber/lc_callnumber)
|
2
|
+
|
3
|
+
# LCCallNumber
|
4
|
+
|
5
|
+
Very simple attempt to work with Library of Congress Call Numbers (_nee_ Classification Numbers).
|
6
|
+
Includes simple getters/setters and a PEG parser.
|
7
|
+
|
8
|
+
|
9
|
+
## Things that aren't yet done
|
10
|
+
|
11
|
+
* Normalization: create a string that can be compared with other normalized strings to correctly order the call numbers
|
12
|
+
* Implement `<=>` so call number object can be compared.
|
13
|
+
* Much better testing
|
14
|
+
|
15
|
+
|
16
|
+
## Parts of an LC Call Number
|
17
|
+
|
18
|
+
LC Call Numbers are used in libraries to classify and order books and other items.
|
19
|
+
|
20
|
+
Some (increasingly complex) samples are:
|
21
|
+
|
22
|
+
* A1
|
23
|
+
* A1.2 .B3
|
24
|
+
* A1.2 .B3 .C4
|
25
|
+
* A1.2 .B3 1990
|
26
|
+
* A1.2 1888 .B3 .C4 2000
|
27
|
+
* A1.2 .A54 21st 2010
|
28
|
+
|
29
|
+
The OCLC has [a page explaining how LC Call Numbers are composed](http://www.oclc.org/bibformats/en/0xx/050.html), which you should reference if you feel the need.
|
30
|
+
|
31
|
+
For purposes of this class, an LC Call Number consists of the following parts. Only the first two are required; everything else is optional.
|
32
|
+
|
33
|
+
* __Letter(s).__ One or more letters
|
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).
|
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
|
+
* __Doon2__. Another date or other number
|
38
|
+
* __"Extra" Cutters__. The 2nd through Nth Cutter numbers, lumped together because we don't have to worry about them getting interspersed with doons.
|
39
|
+
* __Year__. The year of publication
|
40
|
+
* __"Rest"__. Everything and anything else.
|
41
|
+
|
42
|
+
## Usage
|
43
|
+
|
44
|
+
In general, you won't be building these things up by hand; you'll try to parse them.
|
45
|
+
|
46
|
+
Note that in the case below, while in theory this could be a callnumber with a single cutter followed by a doon2 and no year, we presume the '1990' is a year and don't call it a doon.
|
47
|
+
|
48
|
+
~~~ruby
|
49
|
+
|
50
|
+
require 'lc_callnumber'
|
51
|
+
|
52
|
+
lc = LCCallNumber.parse('A1.2 .B3 1990') #=> LCCallNumber::CallNumber
|
53
|
+
lc.letters #=> 'A'
|
54
|
+
lc.digits #=> 1.2 (as a float for a float; int if there's no decimal)
|
55
|
+
lc.doon1 #=> nil
|
56
|
+
lc.firstcutter #=> Cutter<letter=>'B', digits=>'3', dot=>true>
|
57
|
+
lc.extra_cutters #=> []
|
58
|
+
lc.year #=> 1990
|
59
|
+
lc.rest #=> nil
|
60
|
+
|
61
|
+
lc = LCCallNumber.parse('A .B3') #=> LCCallNumber::UnparseableCallNumber
|
62
|
+
lc.valid? #=> false
|
63
|
+
|
64
|
+
~~~
|
65
|
+
|
66
|
+
|
67
|
+
## Installation
|
68
|
+
|
69
|
+
Add this line to your application's Gemfile:
|
70
|
+
|
71
|
+
gem 'lc_callnumber'
|
72
|
+
|
73
|
+
And then execute:
|
74
|
+
|
75
|
+
$ bundle
|
76
|
+
|
77
|
+
Or install it yourself as:
|
78
|
+
|
79
|
+
$ gem install lc_callnumber
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
## Contributing
|
86
|
+
|
87
|
+
1. Fork it
|
88
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
89
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
90
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
91
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lc_callnumber/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "lc_callnumber"
|
8
|
+
spec.version = LCCallNumber::VERSION
|
9
|
+
spec.authors = ["Bill Dueber"]
|
10
|
+
spec.email = ["bill@dueber.com"]
|
11
|
+
spec.description = %q{Work with LC Call (Classification) Numbers}
|
12
|
+
spec.summary = %q{Work with LC Call (Classification) Numbers, including an attempt to parse them out from a string to their component parts}
|
13
|
+
spec.homepage = "https://github.com/billdueber/lc_callnumber"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'parslet'
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "minitest"
|
26
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'lc_callnumber/parser'
|
2
|
+
|
3
|
+
module LCCallNumber
|
4
|
+
|
5
|
+
@parser = Parser.new
|
6
|
+
@transformer = Transform.new
|
7
|
+
|
8
|
+
def self.parse(i)
|
9
|
+
return UnparseableCallNumber.new(i) if i.nil?
|
10
|
+
i.chomp!
|
11
|
+
i.strip!
|
12
|
+
orig = i
|
13
|
+
i.gsub!(/\[(.+)\]/, "$1")
|
14
|
+
i.gsub!('\\', ' ')
|
15
|
+
|
16
|
+
# We also have a bunch that start with a slash and have slashes where you'd
|
17
|
+
# expect logical breaks. Don't know why.
|
18
|
+
|
19
|
+
if i =~ /\A\//
|
20
|
+
i.gsub!('/', ' ')
|
21
|
+
end
|
22
|
+
|
23
|
+
# Ditch leading + or *
|
24
|
+
i.gsub!(/\A[+*]/, '')
|
25
|
+
|
26
|
+
# Strip off any leading/trailing spaces and upcase it
|
27
|
+
i.strip!
|
28
|
+
i.upcase!
|
29
|
+
|
30
|
+
# Don't bother if it starts with a number, four letters, or the string 'law'
|
31
|
+
# Don't bother if there's no numbers at all
|
32
|
+
if (/\A\d/.match i) or
|
33
|
+
(/\A[[:alpha:]]{4}/.match i) or
|
34
|
+
(/\ALAW/i.match i) or
|
35
|
+
!(/\d/.match i)
|
36
|
+
return UnparseableCallNumber.new(orig)
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
p = @parser.parse(i)
|
41
|
+
# puts p
|
42
|
+
lc = @transformer.apply(p)
|
43
|
+
lc.original_string = orig
|
44
|
+
return lc
|
45
|
+
rescue Parslet::ParseFailed => e
|
46
|
+
return UnparseableCallNumber.new(orig)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
# The "lead" -- initial letter(s) and digit(s)
|
57
|
+
class Lead
|
58
|
+
attr_accessor :letters, :digits
|
59
|
+
def initialize(l, d)
|
60
|
+
@letters = l
|
61
|
+
@digits = d
|
62
|
+
end
|
63
|
+
def inspect
|
64
|
+
"Lead<#{letters}, #{digits.inspect}>"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# A generic "Decimal" part, where we keep the
|
69
|
+
# integer and fractional parts separate so
|
70
|
+
# we can construct a string more easily
|
71
|
+
|
72
|
+
class Decimal
|
73
|
+
attr_accessor :intpart, :fractpart
|
74
|
+
def initialize(i, f=nil)
|
75
|
+
@intpart = i
|
76
|
+
@fractpart = f
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_num
|
80
|
+
fractpart ? "#{intpart}.#{fractpart}".to_f : intpart.to_i
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
fractpart ? "#{intpart}.#{fractpart}" : intpart
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def inspect
|
89
|
+
if fractpart
|
90
|
+
"#{intpart}:#{fractpart}"
|
91
|
+
else
|
92
|
+
"#{intpart}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# A "Cutter" is a single letter followed by one or more digits
|
98
|
+
# It is sometimes preceded by a decimal point. We note whether or not
|
99
|
+
# we got a decimal point because it may be a hint as to whether or not
|
100
|
+
# we're actually looking at a Cutter.
|
101
|
+
|
102
|
+
class Cutter
|
103
|
+
attr_accessor :letter, :digits, :dot
|
104
|
+
def initialize(l,d, dot=nil)
|
105
|
+
@letter = l
|
106
|
+
@digits = d
|
107
|
+
@dot = !dot.nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
def inspect
|
111
|
+
"Cut<#{letter},#{digits}>"
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_s
|
115
|
+
"#{letter}#{digits}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Some call numbers have a year, infantry division, etc. preceeding
|
121
|
+
# and/or following the first cutter. We take all three as a unit so
|
122
|
+
# we can deal with edge cases more easily.
|
123
|
+
#
|
124
|
+
# "doon" in this case is "Date Or Other Number"
|
125
|
+
|
126
|
+
class FirstCutterSet
|
127
|
+
attr_accessor :doon1, :cutter, :doon2
|
128
|
+
def initialize(d1, cutter, d2)
|
129
|
+
@doon1 = d1
|
130
|
+
@doon2 = d2
|
131
|
+
@cutter = cutter
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Bring it all together in the callnumber
|
136
|
+
# We feed it the parts we have, and then reassign fields based on
|
137
|
+
# what we have.
|
138
|
+
#
|
139
|
+
# To wit:
|
140
|
+
# Q123 .C4 1990 --- published in 1990
|
141
|
+
# Q123 .C4 1990 .D5 2003 -- published in 2003 with a 2nd doon of 1990
|
142
|
+
|
143
|
+
|
144
|
+
class CallNumber
|
145
|
+
|
146
|
+
attr_accessor :letters, :digits, :doon1, :firstcutter, :doon2,
|
147
|
+
:extra_cutters, :year, :rest
|
148
|
+
attr_accessor :original_string
|
149
|
+
|
150
|
+
|
151
|
+
def initialize(lead, fcset=nil, ec=[], year=nil, rest=nil)
|
152
|
+
@letters = lead.letters
|
153
|
+
@digits = lead.digits.to_num
|
154
|
+
|
155
|
+
if fcset
|
156
|
+
@doon1 = fcset.doon1
|
157
|
+
@doon2 = fcset.doon2
|
158
|
+
@firstcutter = fcset.cutter
|
159
|
+
end
|
160
|
+
|
161
|
+
@extra_cutters = ec
|
162
|
+
@year = year
|
163
|
+
@rest = rest
|
164
|
+
|
165
|
+
@rest.strip! if @rest
|
166
|
+
|
167
|
+
self.reassign_fields
|
168
|
+
end
|
169
|
+
|
170
|
+
def reassign_fields
|
171
|
+
# If we've got a doon2, but no year and no extra cutters,
|
172
|
+
# put the doon2 in the year
|
173
|
+
|
174
|
+
if doon2 and (doon2=~ /\A\d+\Z/) and extra_cutters.empty? and year.nil?
|
175
|
+
@year = @doon2
|
176
|
+
@doon2 = nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def inspect
|
181
|
+
header = %w(letters digits doon1 cut1 doon2 othercut)
|
182
|
+
actual = [letters,digits,doon1,firstcutter,doon2,extra_cutters.join(','),year,rest].map{|x| x.to_s}
|
183
|
+
fmt = '%-7s %-9s %-6s %-6s %-6s %-20s year=%-6s rest=%-s'
|
184
|
+
[('%-7s %-9s %-6s %-6s %-6s %-s' % header), (fmt % actual)].join("\n")
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
def valid?
|
190
|
+
@letters && @digits
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
class UnparseableCallNumber < CallNumber
|
196
|
+
|
197
|
+
def initialize(str)
|
198
|
+
self.original_string = str
|
199
|
+
self.extra_cutters = []
|
200
|
+
end
|
201
|
+
|
202
|
+
def valid?
|
203
|
+
false
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
|
214
|
+
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
require 'lc_callnumber/lcc'
|
3
|
+
|
4
|
+
module LCCallNumber
|
5
|
+
class Parser < Parslet::Parser
|
6
|
+
rule(:space) { match('\s').repeat(1) }
|
7
|
+
rule(:space?) { space.maybe }
|
8
|
+
rule(:dot) { str('.') }
|
9
|
+
rule(:dot?) { dot.maybe }
|
10
|
+
|
11
|
+
rule(:digit) { match('\d') }
|
12
|
+
rule(:digits) { digit.repeat(1) }
|
13
|
+
rule(:digits?) { digits.maybe }
|
14
|
+
|
15
|
+
rule(:year) { digits }
|
16
|
+
|
17
|
+
rule(:firstletter) { match('(?i:[A-HJ-NP-VZ])') }
|
18
|
+
rule(:letter) { match('(?i:[A-Z])') }
|
19
|
+
rule(:a2) { firstletter >> letter.maybe }
|
20
|
+
rule(:a3) {
|
21
|
+
match['K'] >> letter.repeat(2) |
|
22
|
+
str('DAW') |
|
23
|
+
str('DJK')
|
24
|
+
}
|
25
|
+
|
26
|
+
rule(:alpha) {
|
27
|
+
a3 |
|
28
|
+
a2
|
29
|
+
}
|
30
|
+
|
31
|
+
# Numeric string may or may not have a dot
|
32
|
+
rule(:numstring) {
|
33
|
+
digits.as(:intpart) >> (dot >> digits.as(:fractpart)).maybe
|
34
|
+
}
|
35
|
+
|
36
|
+
# So, the "lead" is what I'm calling the necessary part of the callnumber,
|
37
|
+
# the letter(s) and following number
|
38
|
+
rule(:lead) {
|
39
|
+
(alpha.as(:alpha) >> space? >> numstring.as(:numbers)).as(:lead)
|
40
|
+
#|
|
41
|
+
#(alpha.as(:alpha) >> space).as(:lead)
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
# "Date or other number" -- need to take into account 1st, 2nd, etc.
|
46
|
+
# Should probably restrict this so that, e.g., you can't have "2rd"
|
47
|
+
# but for now this is sufficient.
|
48
|
+
rule(:suffix) {
|
49
|
+
str('ST') |
|
50
|
+
str('ND') |
|
51
|
+
str('RD') |
|
52
|
+
str('TH')
|
53
|
+
}
|
54
|
+
|
55
|
+
rule(:doon) {
|
56
|
+
dot? >> digits.as(:digits) >> space? >> suffix.as(:suffix) |
|
57
|
+
digits.as(:digits)
|
58
|
+
}
|
59
|
+
rule(:doon?) { doon.maybe }
|
60
|
+
|
61
|
+
# Cutter is a letter followed by numbers
|
62
|
+
rule(:dotcutter) { (dot.as(:dot) >> letter.as(:letter) >> digits.as(:digits)).as(:cutter) }
|
63
|
+
rule(:odotcutter) { ((dot.maybe).as(:dot) >> letter.as(:letter) >> digits.as(:digits)).as(:cutter) }
|
64
|
+
|
65
|
+
# # Firstcutter has to have a dot
|
66
|
+
# rule(:dotcutter) { dot >> cutter }
|
67
|
+
# rule(:odotcutter) { (dot.maybe).as(:dot) >> cutter}
|
68
|
+
|
69
|
+
# The first cutter set can have doons on either side
|
70
|
+
rule(:firstcutterset) {
|
71
|
+
((doon >> space).maybe).as(:doon1) >> odotcutter.as(:cutter) >> ((space >> doon).maybe).as(:doon2)
|
72
|
+
}
|
73
|
+
|
74
|
+
# Other cutters may or may not have a dot
|
75
|
+
rule(:extracutters) {
|
76
|
+
(space? >> odotcutter).repeat(0)
|
77
|
+
}
|
78
|
+
|
79
|
+
rule(:cutters) {
|
80
|
+
odotcutter >> extracutters
|
81
|
+
}
|
82
|
+
|
83
|
+
# The rest of it, if there is anything but spaces
|
84
|
+
rule(:rest) {
|
85
|
+
space? >> any.repeat(1)
|
86
|
+
}
|
87
|
+
|
88
|
+
rule(:lcclass) {
|
89
|
+
lead >> space? >> (firstcutterset).as(:fcset) >> extracutters.as(:ec) >> ((space >> year.as(:yeardigits)).maybe).as(:year) >> (rest.maybe).as(:rest) |
|
90
|
+
lead >> ((space >> year.as(:yeardigits)).maybe).as(:year) >> (rest.maybe).as(:rest)
|
91
|
+
}
|
92
|
+
root(:lcclass)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
class Transform < Parslet::Transform
|
98
|
+
rule(:intpart=>simple(:i), :fractpart=>simple(:f)) { Decimal.new(i,f) }
|
99
|
+
rule(:intpart=>simple(:i)) { Decimal.new(i,nil) }
|
100
|
+
|
101
|
+
rule(:alpha=>simple(:a), :numbers=>simple(:n)) { @a = Lead.new(a.to_s,n) }
|
102
|
+
rule(:digits=>simple(:d), :suffix=>simple(:s)) {"#{d}#{s}"}
|
103
|
+
rule(:digits=>simple(:d)) { d.to_s }
|
104
|
+
rule(:doon => simple(:d)) { d.to_s.strip }
|
105
|
+
rule(:cutter => {:dot=>simple(:dot), :letter=>simple(:l), :digits=>simple(:d)}) { Cutter.new(l.to_s,d.to_s, !(dot.nil?))}
|
106
|
+
|
107
|
+
rule(:doon1=>simple(:d1), :doon2=>simple(:d2), :cutter=>simple(:c)) { FirstCutterSet.new(d1, c, d2)}
|
108
|
+
rule(:yeardigits => simple(:y)) { y.nil? ? nil : y.to_s.strip}
|
109
|
+
rule(:lead=>simple(:l), :fcset=>simple(:fc), :ec=>sequence(:ec), :year=>simple(:year), :rest=>simple(:rest)) { CallNumber.new(l,fc,ec,year,rest.to_s)}
|
110
|
+
rule(:lead=>simple(:l), :year=>simple(:year), :rest=>simple(:rest)) {CallNumber.new(l,nil,[],year,rest.to_s)}
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'lc_callnumber'
|
3
|
+
|
4
|
+
|
5
|
+
def test_data(relative_path)
|
6
|
+
return File.expand_path(File.join("test_data", relative_path), File.dirname(__FILE__))
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'minitest/spec'
|
10
|
+
require 'minitest/autorun'
|
11
|
+
|
12
|
+
|
13
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#Tab-delimited file, exported from https://docs.google.com/spreadsheet/ccc?key=0AvHn-9AM-38ldE5TYjFvWFFTdnU5emltMEhuQzEyRnc#gid=0
|
2
|
+
# String Letter(s) Number Doon1 Cutter1 Doon2 Other,Cutters PubYear Rest
|
3
|
+
A1 A 1
|
4
|
+
A1 .C2 A 1 C2
|
5
|
+
A1 C2 A 1 C2
|
6
|
+
A10.4 A 10.4
|
7
|
+
A10.4 C345 A 10.4 C345
|
8
|
+
A1 1990 A 1 1990
|
9
|
+
A 1 C22 A 1 C22
|
10
|
+
BF 173 .F889 F92 BF 173 F889 F92
|
11
|
+
HD7123 .A5 1970 b HD 7123 A5 1970 b
|
12
|
+
PJ5054.B5N44 I83 PJ 5054 B5 N44,I83
|
13
|
+
DA 690 .B8 A3 v.25 DA 690 B8 A3 v.25
|
14
|
+
PN 1995.9 .N3 N75 1970 PN 1995.9 N3 N75 1970
|
15
|
+
D570.3 28th .P96 D 570.3 28TH P96
|
16
|
+
UA704 .C9 12 th R89 UA 704 C9 12th R89
|
17
|
+
E517.5 21st .A54 E 517.5 21st A54
|
18
|
+
D761.1 .1st L36 D 761.1 1st L36
|
19
|
+
G 9801 .A4 s250 .G4 ST5-8/11 G 9801 A4 S250,G4 ST5-8/11
|
20
|
+
HD 8039 .R52 I4 I42 HD 8039 R52 I4,I42
|
21
|
+
ND 623 .L58 A43 1970 ND 623 L58 A43 1970
|
22
|
+
DK265.8.L4 L573 DK 265.8 L4 L573
|
23
|
+
A1 1888 .B2 1990 A 1 1888 B2 1990
|
24
|
+
A1 B2 1888 1990 A 1 B2 1888 1990
|
25
|
+
A1 1888 B2 12th A 1 1888 B2 12TH
|
26
|
+
A1 B2 1990 v.3 A 1 B2 1990 v.3
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe "Metadata" do
|
4
|
+
it "has a version number" do
|
5
|
+
assert ::LCCallNumber::VERSION
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "Basics" do
|
10
|
+
it "can parse all the items in the basics file" do
|
11
|
+
File.open(test_data('basics.txt')).each_line do |line|
|
12
|
+
next if line =~ /\A#/
|
13
|
+
next unless line =~ /\S/
|
14
|
+
line.chomp!
|
15
|
+
orig,letters,digits,doon1,c1,doon2,other_cutters,pubyear,rest = line.split(/\t/).map{|s| s.upcase.strip.to_s}
|
16
|
+
lc = LCCallNumber.parse(orig)
|
17
|
+
assert_equal letters.to_s, lc.letters, "#{orig} -> letters #{lc.inspect}"
|
18
|
+
assert_equal digits.to_s, lc.digits.to_s, "#{orig} -> numbers #{lc.inspect}"
|
19
|
+
assert_equal doon1.to_s, lc.doon1.to_s, "#{orig} -> doon1 #{lc.inspect}"
|
20
|
+
assert_equal c1.to_s, lc.firstcutter.to_s, "#{orig} -> first cutter #{lc.inspect}"
|
21
|
+
assert_equal doon2.to_s, lc.doon2.to_s, "#{orig} -> doon2 #{lc.inspect}"
|
22
|
+
assert_equal other_cutters.to_s.split(/\s*,\s*/), lc.extra_cutters.map(&:to_s), "#{orig} -> extra_cutters #{lc.inspect}"
|
23
|
+
assert_equal pubyear.to_s, lc.year.to_s, "#{orig} -> year #{lc.inspect}"
|
24
|
+
assert_equal rest.to_s, lc.rest.to_s, "#{orig} -> rest #{lc.inspect}"
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lc_callnumber
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bill Dueber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parslet
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Work with LC Call (Classification) Numbers
|
70
|
+
email:
|
71
|
+
- bill@dueber.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- lc_callnumber.gemspec
|
83
|
+
- lib/lc_callnumber.rb
|
84
|
+
- lib/lc_callnumber/lcc.rb
|
85
|
+
- lib/lc_callnumber/parser.rb
|
86
|
+
- lib/lc_callnumber/version.rb
|
87
|
+
- test/minitest_helper.rb
|
88
|
+
- test/test_data/basics.txt
|
89
|
+
- test/test_lc_callnumber.rb
|
90
|
+
homepage: https://github.com/billdueber/lc_callnumber
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata: {}
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.2.0
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Work with LC Call (Classification) Numbers, including an attempt to parse
|
114
|
+
them out from a string to their component parts
|
115
|
+
test_files:
|
116
|
+
- test/minitest_helper.rb
|
117
|
+
- test/test_data/basics.txt
|
118
|
+
- test/test_lc_callnumber.rb
|