pennmarc 1.0.0 → 1.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.
@@ -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