pennmarc 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,79 +2,53 @@
2
2
 
3
3
  require 'active_support/all'
4
4
  require_relative 'helpers/helper'
5
- require_relative 'helpers/creator'
6
- require_relative 'helpers/database'
7
- require_relative 'helpers/date'
8
- require_relative 'helpers/format'
9
- require_relative 'helpers/genre'
10
- require_relative 'helpers/identifier'
11
- require_relative 'helpers/language'
12
- require_relative 'helpers/link'
13
- require_relative 'helpers/location'
14
- require_relative 'helpers/subject'
15
- require_relative 'helpers/title'
16
- require_relative 'helpers/citation'
17
- require_relative 'helpers/relation'
18
- require_relative 'helpers/production'
19
- require_relative 'helpers/edition'
20
- require_relative 'helpers/note'
21
- require_relative 'helpers/series'
5
+
6
+ # Require all files in helpers directory
7
+ # TODO: this double-requires Helper, but that needs to be required before other helpers...
8
+ Dir[File.join(__dir__, 'helpers', '*.rb')].each { |file| require file }
22
9
 
23
10
  # Top level gem namespace
24
11
  module PennMARC
25
- attr_accessor :mappings
26
-
27
- DEFINED_HELPERS = %w[Creator Database Date Format Genre Language Link Location Subject Title Relation].freeze
28
-
29
- # Methods here should return values used in the indexer. The parsing logic should
30
- # NOT return values specific to any particular site/interface, but just general
31
- # MARC parsing logic for "title", "subject", "author", etc., as much as reasonably
32
- # possible. We'll see how it goes.
33
- #
34
- # Methods should, by default, take in a MARC::Record
12
+ # Access point for magic calls to helper methods
35
13
  class Parser
36
- def initialize(helpers: DEFINED_HELPERS)
37
- @mappings = {}
38
- @helpers = Array.wrap(helpers) # TODO: load helpers dynamically?
14
+ # Allow calls to `respond_to?` on parser instances to respond accurately by checking helper classes
15
+ # @param [String, Symbol] name
16
+ # @return [Boolean]
17
+ def respond_to_missing?(name, include_private = false)
18
+ helper, method_name = parse_call(name)
19
+ begin
20
+ "PennMARC::#{helper}".constantize.respond_to?(method_name)
21
+ rescue NameError
22
+ super # Helper is not defined, so check self
23
+ end
39
24
  end
40
25
 
41
- def respond_to_missing?(name)
42
- name.split('_').first.in? @helpers
43
- end
44
-
45
- # Call helper class methods, e.g.,
26
+ # Call helper class methods, passing along additional arguments as needed, e.g.:
46
27
  # #title_show -> PennMARC::Title.show
47
28
  # #subject_facet -> PennMARC::Subject.facet
48
- def method_missing(name, opts)
49
- call = name.to_s.split('_')
50
- helper = call.shift
51
- meth = call.join('_')
52
- "PennMARC::#{helper.titleize}".constantize.public_send(meth, opts)
29
+ # @param [Symbol] name
30
+ # @param [MARC::Record] record
31
+ # @param [Array] opts
32
+ def method_missing(name, record, *opts)
33
+ helper, method_name = parse_call(name)
34
+ raise NoMethodError unless helper && method_name
35
+
36
+ helper_klass = "PennMARC::#{helper.titleize}".constantize
37
+ if opts.any?
38
+ helper_klass.public_send(method_name, record, **opts.first)
39
+ else
40
+ helper_klass.public_send(method_name, record)
41
+ end
53
42
  end
54
43
 
55
- # Load language map from YAML and memoize in @mappings hash
56
- # @return [Hash]
57
- def language_map
58
- @mappings[:language] ||= load_map('language.yml')
59
- end
60
-
61
- # Load location map from YAML and memoize in @mappings hash
62
- # @return [Hash]
63
- def location_map
64
- @mappings[:location] ||= load_map('locations.yml')
65
- end
44
+ private
66
45
 
67
- # Load relator map from YAML and memoize in @mappings hash
68
- # @return [Hash]
69
- def relator_map
70
- @mappings[:relator] ||= load_map('relator.yml')
71
- end
72
-
73
- # @param [String] filename of mapping file in config directory, with file extension
74
- # @return [Hash] mapping as hash
75
- def load_map(filename)
76
- YAML.safe_load(File.read(File.join(File.expand_path(__dir__), 'mappings', filename)),
77
- symbolize_names: true)
46
+ # Parse out a method call name in the way method_missing is configured to handle
47
+ # @param [String, Symbol] name
48
+ # @return [Array]
49
+ def parse_call(name)
50
+ call = name.to_s.split('_')
51
+ [call.shift&.titleize, call.join('_').to_sym]
78
52
  end
79
53
  end
80
54
  end
data/lib/pennmarc/util.rb CHANGED
@@ -9,8 +9,10 @@ module PennMARC
9
9
  # @param [MARC::DataField] field
10
10
  # @param [Proc] selector
11
11
  # @return [String]
12
- def join_subfields(field, &)
13
- field.select(&).filter_map { |sf|
12
+ def join_subfields(field, &selector)
13
+ return '' unless field
14
+
15
+ field.select(&selector).filter_map { |sf|
14
16
  value = sf.value&.strip
15
17
  next if value.blank?
16
18
 
@@ -26,7 +28,7 @@ module PennMARC
26
28
  # @param [Regexp] regex
27
29
  # @return [TrueClass, FalseClass]
28
30
  def subfield_value?(field, subfield, regex)
29
- field.any? { |sf| sf.code == subfield.to_s && sf.value =~ regex }
31
+ field&.any? { |sf| sf.code == subfield.to_s && sf.value =~ regex }
30
32
  end
31
33
 
32
34
  # returns true if a given field has a given subfield value in a given array
@@ -49,7 +51,6 @@ module PennMARC
49
51
  end
50
52
 
51
53
  # returns a lambda checking if passed-in subfield's code is a member of array
52
- # TODO: include lambda returning methods in their own module?
53
54
  # @param [Array] array
54
55
  # @return [Proc]
55
56
  def subfield_in?(array)
@@ -57,7 +58,6 @@ module PennMARC
57
58
  end
58
59
 
59
60
  # returns a lambda checking if passed-in subfield's code is NOT a member of array
60
- # TODO: include lambda returning methods in their own module?
61
61
  # @param [Array] array
62
62
  # @return [Proc]
63
63
  def subfield_not_in?(array)
@@ -124,16 +124,15 @@ module PennMARC
124
124
  # See: https://www.loc.gov/marc/bibliographic/bd880.html
125
125
  # @param [MARC::Record] record
126
126
  # @param [String|Array] subfield6_value either a string to look for in sub6 or an array of them
127
- # @param selector [Proc] takes a subfield as argument, returns a boolean
127
+ # @param [Proc] selector takes a subfield as argument, returns a boolean
128
128
  # @return [Array] array of linked alternates
129
- def linked_alternate(record, subfield6_value, &)
129
+ def linked_alternate(record, subfield6_value, &selector)
130
130
  record.fields('880').filter_map do |field|
131
131
  next unless subfield_value?(field, '6', /^#{Array.wrap(subfield6_value).join('|')}/)
132
132
 
133
- field.select(&).map(&:value).join(' ')
133
+ field.select(&selector).map(&:value).join(' ')
134
134
  end
135
135
  end
136
- alias get_880 linked_alternate
137
136
 
138
137
  # Common case of wanting to extract all the subfields besides 6 or 8,
139
138
  # from 880 datafield that has a particular subfield 6 value. We exclude 6 because
@@ -142,8 +141,9 @@ module PennMARC
142
141
  # @param [String|Array] subfield6_value either a string to look for in sub6 or an array of them
143
142
  # @return [Array] array of linked alternates without 8 or 6 values
144
143
  def linked_alternate_not_6_or_8(record, subfield6_value)
144
+ excluded_subfields = %w[6 8]
145
145
  linked_alternate(record, subfield6_value) do |sf|
146
- %w[6 8].exclude?(sf.code)
146
+ excluded_subfields.exclude?(sf.code)
147
147
  end
148
148
  end
149
149
 
@@ -179,7 +179,6 @@ module PennMARC
179
179
  def join_and_squish(array)
180
180
  array.join(' ').squish
181
181
  end
182
- alias join_and_trim_whitespace join_and_squish
183
182
 
184
183
  # If there's a subfield i, extract its value, and if there's something
185
184
  # in parentheses in that value, extract that.
data/pennmarc.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'pennmarc'
5
- s.version = '1.0.0'
5
+ s.version = '1.0.1'
6
6
  s.summary = 'Penn Libraries Catalog MARC parsing wisdom for cross-project usage'
7
7
  s.description = 'This gem provides methods for parsing a Penn Libraries MARCXML record into string, array and date
8
8
  objects for use in discovery or preservation applications.'
@@ -17,9 +17,9 @@ describe 'PennMARC::Creator' do
17
17
  end
18
18
 
19
19
  it 'contains the expected search field values for a single author work' do
20
- expect(helper.search(record, mapping)).to eq ['Name Surname http://cool.uri/12345 author 1900-2000.',
21
- 'Surname, Name http://cool.uri/12345 author 1900-2000.',
22
- 'Alternative Surname']
20
+ expect(helper.search(record, relator_map: mapping)).to eq ['Name Surname http://cool.uri/12345 author 1900-2000.',
21
+ 'Surname, Name http://cool.uri/12345 author 1900-2000.',
22
+ 'Alternative Surname']
23
23
  end
24
24
  end
25
25
 
@@ -30,8 +30,8 @@ describe 'PennMARC::Creator' do
30
30
  end
31
31
 
32
32
  it 'contains the expected search field values for a corporate author work' do
33
- expect(helper.search(record, mapping)).to eq ['Group of People Annual Meeting Author.',
34
- 'Alt. Group Name Alt. Annual Meeting']
33
+ expect(helper.search(record, relator_map: mapping)).to eq ['Group of People Annual Meeting Author.',
34
+ 'Alt. Group Name Alt. Annual Meeting']
35
35
  end
36
36
  end
37
37
  end
@@ -46,7 +46,7 @@ describe 'PennMARC::Creator' do
46
46
  end
47
47
 
48
48
  it 'returns values for the author, including mapped relator code from ǂ4' do
49
- values = helper.values(record, mapping)
49
+ values = helper.values(record, relator_map: mapping)
50
50
  expect(values).to contain_exactly 'Author Fancy active 24th century AD, Author.'
51
51
  expect(values.join.downcase).not_to include 'alt'
52
52
  end
@@ -58,7 +58,7 @@ describe 'PennMARC::Creator' do
58
58
  end
59
59
 
60
60
  it 'returns values for the corporate author, including mapped relator code from ǂ4' do
61
- expect(helper.values(record, mapping)).to contain_exactly 'Annual Report Leader author, Author.'
61
+ expect(helper.values(record, relator_map: mapping)).to contain_exactly 'Annual Report Leader author, Author.'
62
62
  end
63
63
  end
64
64
  end
@@ -160,7 +160,7 @@ describe 'PennMARC::Creator' do
160
160
  end
161
161
 
162
162
  it 'returns conference name information for display, ignoring any linked 880 fields' do
163
- expect(helper.conference_show(record, mapping)).to eq ['MARC History Symposium, Author.']
163
+ expect(helper.conference_show(record, relator_map: mapping)).to eq ['MARC History Symposium, Author.']
164
164
  end
165
165
  end
166
166
 
@@ -204,7 +204,7 @@ describe 'PennMARC::Creator' do
204
204
  end
205
205
 
206
206
  it 'returns expected contributor values' do
207
- expect(helper.contributor_show(record, mapping)).to contain_exactly(
207
+ expect(helper.contributor_show(record, relator_map: mapping)).to contain_exactly(
208
208
  'Name I laureate 1968 pseud Fuller Name author affiliation materials, Author',
209
209
  'Corporation A division Office 1968 author affiliation materials, Author',
210
210
  'Alt Name Alt num Alt title Alt date Alt qualifier Alt Fuller Name Alt relator Alt affiliation Alt materials',
@@ -28,8 +28,9 @@ describe 'PennMARC::Edition' do
28
28
 
29
29
  describe '.other_show' do
30
30
  it 'returns other edition values' do
31
- expect(helper.other_show(record, mapping)).to contain_exactly('Autre Editione',
32
- 'Other Edition: Author. (Cool Book)')
31
+ expect(helper.other_show(record, relator_map: mapping)).to(
32
+ contain_exactly('Autre Editione', 'Other Edition: Author. (Cool Book)')
33
+ )
33
34
  end
34
35
  end
35
36
  end
@@ -7,7 +7,7 @@ describe 'PennMARC::Format' do
7
7
 
8
8
  describe '.facet' do
9
9
  let(:map) { location_map }
10
- let(:formats) { helper.facet(record, map) }
10
+ let(:formats) { helper.facet(record, location_map: map) }
11
11
 
12
12
  context 'with an "Archive"' do
13
13
  let(:map) do
@@ -43,7 +43,7 @@ describe 'PennMARC::Genre' do
43
43
  end
44
44
 
45
45
  describe '.facet' do
46
- let(:values) { helper.facet(record, location_map) }
46
+ let(:values) { helper.facet(record, location_map: location_map) }
47
47
  let(:location_map) do
48
48
  { manu: { specific_location: 'Secure Manuscripts Storage' },
49
49
  vanp: { specific_location: 'Van Pelt' } }
@@ -18,7 +18,7 @@ describe 'PennMARC::Language' do
18
18
 
19
19
  describe '.search' do
20
20
  it 'returns the expected display value' do
21
- expect(helper.search(record, mapping)).to eq 'English'
21
+ expect(helper.search(record, language_map: mapping)).to eq 'English'
22
22
  end
23
23
  end
24
24
 
@@ -11,9 +11,9 @@ describe 'PennMARC::Location' do
11
11
  let(:record) { marc_record(fields: [marc_field(tag: 'itm', subfields: { g: 'stor' })]) }
12
12
 
13
13
  it 'returns expected value' do
14
- expect(helper.location(record: record, location_mapping: mapping,
14
+ expect(helper.location(record: record, location_map: mapping,
15
15
  display_value: :library)).to contain_exactly('LIBRA')
16
- expect(helper.location(record: record, location_mapping: mapping,
16
+ expect(helper.location(record: record, location_map: mapping,
17
17
  display_value: 'specific_location')).to contain_exactly('LIBRA')
18
18
  end
19
19
  end
@@ -22,9 +22,9 @@ describe 'PennMARC::Location' do
22
22
  let(:record) { marc_record(fields: [marc_field(tag: 'hld', subfields: { c: 'stor' })]) }
23
23
 
24
24
  it 'returns expected value' do
25
- expect(helper.location(record: record, location_mapping: mapping,
25
+ expect(helper.location(record: record, location_map: mapping,
26
26
  display_value: :library)).to contain_exactly('LIBRA')
27
- expect(helper.location(record: record, location_mapping: mapping,
27
+ expect(helper.location(record: record, location_map: mapping,
28
28
  display_value: 'specific_location')).to contain_exactly('LIBRA')
29
29
  end
30
30
  end
@@ -36,7 +36,7 @@ describe 'PennMARC::Location' do
36
36
  end
37
37
 
38
38
  it 'returns item location' do
39
- expect(helper.location(record: record, location_mapping: mapping,
39
+ expect(helper.location(record: record, location_map: mapping,
40
40
  display_value: :library)).to contain_exactly('LIBRA')
41
41
  end
42
42
  end
@@ -45,7 +45,7 @@ describe 'PennMARC::Location' do
45
45
  let(:record) { marc_record(fields: [marc_field(tag: 'itm', subfields: { g: %w[dent] })]) }
46
46
 
47
47
  it 'returns expected value' do
48
- expect(helper.location(record: record, location_mapping: mapping,
48
+ expect(helper.location(record: record, location_map: mapping,
49
49
  display_value: :library)).to contain_exactly('Health Sciences Libraries',
50
50
  'Levy Dental Medicine Library')
51
51
  end
@@ -55,7 +55,7 @@ describe 'PennMARC::Location' do
55
55
  let(:record) { marc_record(fields: [marc_field(tag: '852', subfields: { g: 'stor' })]) }
56
56
 
57
57
  it 'returns expected value' do
58
- expect(helper.location(record: record, location_mapping: mapping, display_value: :library)).to be_empty
58
+ expect(helper.location(record: record, location_map: mapping, display_value: :library)).to be_empty
59
59
  end
60
60
  end
61
61
 
@@ -63,7 +63,7 @@ describe 'PennMARC::Location' do
63
63
  let(:record) { marc_record(fields: [marc_field(tag: 'itm', subfields: { g: 'stor' }), marc_field(tag: 'prt')]) }
64
64
 
65
65
  it 'returns expected value' do
66
- expect(helper.location(record: record, location_mapping: mapping,
66
+ expect(helper.location(record: record, location_map: mapping,
67
67
  display_value: :library)).to contain_exactly('LIBRA', 'Online library')
68
68
  end
69
69
  end
@@ -65,7 +65,7 @@ describe 'PennMARC::Relation' do
65
65
  end
66
66
 
67
67
  it 'returns specified subfield values from specified field with blank indicator2' do
68
- values = helper.related_work_show record, relator_map
68
+ values = helper.related_work_show record, relator_map: relator_map
69
69
  expect(values).to contain_exactly 'Translation of: Some Author Aphorisms, Translator',
70
70
  'Alt. Prefix: Alt. Author Alt. Aphorisms'
71
71
  expect(values).not_to include 'Ignored'
@@ -81,7 +81,7 @@ describe 'PennMARC::Relation' do
81
81
  end
82
82
 
83
83
  it "returns specified subfield values from specified field with '2' in indicator2" do
84
- values = helper.contains_show record, relator_map
84
+ values = helper.contains_show record, relator_map: relator_map
85
85
  expect(values).to contain_exactly 'Alt. Prefix: Alt. Name', 'Container of: Some Author Works, Author'
86
86
  expect(values).not_to include 'Ignored'
87
87
  end
@@ -18,7 +18,7 @@ describe 'PennMARC::Series' do
18
18
 
19
19
  describe '.show' do
20
20
  it 'returns the series' do
21
- expect(helper.show(record, mapping)).to contain_exactly('Bean Bagatolvski 1997- bk. 1',
21
+ expect(helper.show(record, relator_map: mapping)).to contain_exactly('Bean Bagatolvski 1997- bk. 1',
22
22
  'Teachings of the feathered pillow',
23
23
  'Учения пернатой подушки')
24
24
  end
@@ -26,7 +26,7 @@ describe 'PennMARC::Series' do
26
26
 
27
27
  describe '.values' do
28
28
  it 'returns the values' do
29
- expect(helper.values(record, mapping)).to contain_exactly('Bean Bagatolvski 1997- bk. 1.')
29
+ expect(helper.values(record, relator_map: mapping)).to contain_exactly('Bean Bagatolvski 1997- bk. 1.')
30
30
  end
31
31
  end
32
32
 
@@ -10,7 +10,7 @@ describe 'PennMARC::Subject' do
10
10
 
11
11
  describe '.search' do
12
12
  let(:record) { marc_record fields: fields }
13
- let(:values) { helper.search(record, relator_map) }
13
+ let(:values) { helper.search(record, relator_map: relator_map) }
14
14
 
15
15
  context 'with a mix of included and excluded tags' do
16
16
  let(:fields) do
@@ -8,6 +8,27 @@ describe PennMARC::Parser do
8
8
  let(:record) { record_from 'test.xml' }
9
9
 
10
10
  it 'delegates to helper modules properly' do
11
- expect { parser.title_show(record) }.not_to raise_exception
11
+ expect(parser.language_search(record)).to eq 'English'
12
+ end
13
+
14
+ it 'delegates to helper modules properly with extra params' do
15
+ bogus_map = { eng: 'American' }
16
+ expect(parser.language_search(record, language_map: bogus_map)).to eq 'American'
17
+ end
18
+
19
+ it 'raises an exception if the method call is invalid' do
20
+ expect { parser.title(record) }.to raise_error NoMethodError
21
+ expect { parser.title_nope(record) }.to raise_error NoMethodError
22
+ end
23
+
24
+ describe '#respond_to?' do
25
+ it 'returns true if a helper has the expected method' do
26
+ expect(parser).to respond_to :language_search
27
+ end
28
+
29
+ it 'returns false if a helper does not have the expected method' do
30
+ expect(parser).not_to respond_to :language_nope
31
+ expect(parser).not_to respond_to :nope
32
+ end
12
33
  end
13
34
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pennmarc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Kanning
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-07-28 00:00:00.000000000 Z
13
+ date: 2023-08-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -84,9 +84,6 @@ files:
84
84
  - Gemfile
85
85
  - Gemfile.lock
86
86
  - README.md
87
- - legacy/indexer.rb
88
- - legacy/marc.rb
89
- - legacy/test_file_output.json
90
87
  - lib/pennmarc.rb
91
88
  - lib/pennmarc/encoding_level.rb
92
89
  - lib/pennmarc/enriched_marc.rb
@@ -109,6 +106,7 @@ files:
109
106
  - lib/pennmarc/helpers/series.rb
110
107
  - lib/pennmarc/helpers/subject.rb
111
108
  - lib/pennmarc/helpers/title.rb
109
+ - lib/pennmarc/mappers.rb
112
110
  - lib/pennmarc/mappings/language.yml
113
111
  - lib/pennmarc/mappings/locations.yml
114
112
  - lib/pennmarc/mappings/relator.yml