pennmarc 1.2.4 → 1.2.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc24e1fe4c30a2cf49fe06d466deaff8ac61677b69b3ce673cebfb3729282ed7
4
- data.tar.gz: cb7d013328a775f8c09d79cb4400374c4bb5ef5b97056e19c595d29707440469
3
+ metadata.gz: bc89ffad46c996296dc5cd84d8a2b9c81b82653b24c9d16f6aa9e12c9c3c6aaa
4
+ data.tar.gz: 5e1f488f73b07970e4242a7bbffb1536db2f800e73db72d4ad9b33d75375ad38
5
5
  SHA512:
6
- metadata.gz: 15de4dca697023774cdbde7cfd805fab906c21f56d39824539204b796e0e25cac86415c971a73ec7c8bd8e6c9983fa8f9edb0231cbb942ef8880dfd7c968ad6c
7
- data.tar.gz: a0d344dae1edbb7d7d4cd57ce698dd04007dfc30af9a981ff0db8708c5f1afe1b55347ec62ab4f544b51d5e187a95a945584d3c593f58f48804b691b384692b3
6
+ metadata.gz: 61a8aded7159b786a45213d03c4eb17b60eced7dba21e79b8b634a03bccdcd7f1e312f5205431e3215a95fbdba91ad60611312a03939f3c03bfc39b85dc5dc88
7
+ data.tar.gz: 756c4a538eedd1fd7b9bc12a347547863ea8674965c3a87669a32ca39d3c50724af0a17e3d1747e7e3a06385a9c6b55aead58d8c5d091996af5c37ed1c1641ba
data/.gitlab-ci.yml CHANGED
@@ -1,65 +1,34 @@
1
+ ---
1
2
  include:
2
- - project: "devops/gitlab/ci-templates/general"
3
- file:
4
- - ".install_hashicorp_vault.yml"
5
- - ".vault_jwt_auth.yml"
6
- - project: "devops/gitlab/ci-templates/ruby"
7
- ref: "sans-dind"
8
- file:
9
- - ".rspec.yml"
10
- - ".rubocop.yml"
11
- - template: "Workflows/MergeRequest-Pipelines.gitlab-ci.yml"
3
+ - template: Jobs/Secret-Detection.gitlab-ci.yml
12
4
 
13
5
  stages:
14
- - test
15
- - deploy
6
+ - secret_check
7
+ - main_pipeline
16
8
 
17
- rspec_app_test:
18
- stage: test
19
- image: ruby:3.2.2
20
- before_script:
21
- - bundle install -j $(nproc)
22
- extends:
23
- - .rspec
24
- tags:
25
- - build
26
-
27
- rubocop_app_test:
28
- stage: test
29
- image: ruby:3.2.2
30
- before_script:
31
- - bundle install -j $(nproc)
32
- extends:
33
- - .rubocop
9
+ secret_detection:
10
+ stage: secret_check
11
+ allow_failure: false
12
+ artifacts:
13
+ paths:
14
+ - .pipeline.yml
15
+ expire_in: 1 day
34
16
  tags:
35
17
  - build
18
+ rules:
19
+ - when: always
36
20
 
37
- # This step was inspired from Gitlab's example of gem publication:
38
- # https://gitlab.com/gitlab-org/quality/pipeline-common/-/blob/master/ci/gem-release.yml
39
- gem_publication:
40
- stage: deploy
41
- image: ruby:3.2.2
42
- variables:
43
- GEMSPEC_FILE: "${CI_PROJECT_NAME}.gemspec"
44
- before_script:
45
- - !reference [.install_hashicorp_vault, before_script]
46
- - !reference [.vault_jwt_auth, before_script]
47
- - export GEM_HOST_API_KEY="$(vault kv get -field=rubygems_api_key ${VAULT_KV_ENDPOINT}${ENVIRONMENT})"
48
- - |
49
- gem -v
50
- rm -f ./*.gem
51
- [ -f "${GEMSPEC_FILE}" ] || (echo "No ${GEMSPEC_FILE} file found!" && exit 1)
52
- - '[ -n "${GEM_HOST_API_KEY}" ] || (echo "GEM_HOST_API_KEY is not set!" && exit 1)'
53
- script:
54
- - GEM_FILE=$(echo "${CI_PROJECT_NAME}-${CI_COMMIT_TAG:1}.gem")
55
- - |
56
- gem build "${GEMSPEC_FILE}"
57
- [ -f "${GEM_FILE}" ] || (echo "No ${GEM_FILE} file found!" && exit 1)
58
- - '[ "${DISABLE_GEM_PUSH}" == "true" ] || gem push "${GEM_FILE}"'
21
+ main:
22
+ stage: main_pipeline
23
+ needs:
24
+ - secret_detection
25
+ allow_failure: false
26
+ trigger:
27
+ include:
28
+ - artifact: .pipeline.yml
29
+ job: secret_detection
30
+ strategy: depend
59
31
  rules:
60
- - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+(\.[a-zA-Z0-9]+)?$/
61
- artifacts:
62
- paths:
63
- - "*.gem"
64
- tags:
65
- - deploy
32
+ - if: "$CI_MERGE_REQUEST_IID"
33
+ - if: "$CI_COMMIT_TAG"
34
+ - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
data/.pipeline.yml ADDED
@@ -0,0 +1,64 @@
1
+ include:
2
+ - project: "devops/gitlab/ci-templates/general"
3
+ file:
4
+ - ".install_hashicorp_vault.yml"
5
+ - ".vault_jwt_auth.yml"
6
+ - project: "devops/gitlab/ci-templates/ruby"
7
+ ref: "sans-dind"
8
+ file:
9
+ - ".rspec.yml"
10
+ - ".rubocop.yml"
11
+
12
+ stages:
13
+ - test
14
+ - deploy
15
+
16
+ rspec_app_test:
17
+ stage: test
18
+ image: ruby:3.2.2
19
+ before_script:
20
+ - bundle install -j $(nproc)
21
+ extends:
22
+ - .rspec
23
+ tags:
24
+ - build
25
+
26
+ rubocop_app_test:
27
+ stage: test
28
+ image: ruby:3.2.2
29
+ before_script:
30
+ - bundle install -j $(nproc)
31
+ extends:
32
+ - .rubocop
33
+ tags:
34
+ - build
35
+
36
+ # This step was inspired from Gitlab's example of gem publication:
37
+ # https://gitlab.com/gitlab-org/quality/pipeline-common/-/blob/master/ci/gem-release.yml
38
+ gem_publication:
39
+ stage: deploy
40
+ image: ruby:3.2.2
41
+ variables:
42
+ GEMSPEC_FILE: "${CI_PROJECT_NAME}.gemspec"
43
+ before_script:
44
+ - !reference [.install_hashicorp_vault, before_script]
45
+ - !reference [.vault_jwt_auth, before_script]
46
+ - export GEM_HOST_API_KEY="$(vault kv get -field=rubygems_api_key ${VAULT_KV_ENDPOINT}${ENVIRONMENT})"
47
+ - |
48
+ gem -v
49
+ rm -f ./*.gem
50
+ [ -f "${GEMSPEC_FILE}" ] || (echo "No ${GEMSPEC_FILE} file found!" && exit 1)
51
+ - '[ -n "${GEM_HOST_API_KEY}" ] || (echo "GEM_HOST_API_KEY is not set!" && exit 1)'
52
+ script:
53
+ - GEM_FILE=$(echo "${CI_PROJECT_NAME}-${CI_COMMIT_TAG:1}.gem")
54
+ - |
55
+ gem build "${GEMSPEC_FILE}"
56
+ [ -f "${GEM_FILE}" ] || (echo "No ${GEM_FILE} file found!" && exit 1)
57
+ - '[ "${DISABLE_GEM_PUSH}" == "true" ] || gem push "${GEM_FILE}"'
58
+ rules:
59
+ - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+(\.[a-zA-Z0-9]+)?$/
60
+ artifacts:
61
+ paths:
62
+ - "*.gem"
63
+ tags:
64
+ - deploy
data/Gemfile CHANGED
@@ -3,6 +3,7 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'activesupport', '~> 7'
6
+ gem 'lcsort'
6
7
  gem 'library_stdnums', '~> 1.6'
7
8
  gem 'marc', '~> 1.2'
8
9
  gem 'nokogiri', '~> 1.15'
data/Gemfile.lock CHANGED
@@ -13,6 +13,7 @@ GEM
13
13
  i18n (1.13.0)
14
14
  concurrent-ruby (~> 1.0)
15
15
  json (2.6.3)
16
+ lcsort (0.9.1)
16
17
  library_stdnums (1.6.0)
17
18
  marc (1.2.0)
18
19
  rexml
@@ -111,6 +112,7 @@ PLATFORMS
111
112
 
112
113
  DEPENDENCIES
113
114
  activesupport (~> 7)
115
+ lcsort
114
116
  library_stdnums (~> 1.6)
115
117
  marc (~> 1.2)
116
118
  nokogiri (~> 1.15)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'lcsort'
4
+
3
5
  module PennMARC
4
6
  # Generates library of congress and dewey classifications using call number data.
5
7
  class Classification < Helper
@@ -42,26 +44,53 @@ module PennMARC
42
44
  }.uniq
43
45
  end
44
46
 
47
+ # Return a normalized Call Number value for sorting purposes. This uses the Lcsort gem normalization logic, which
48
+ # returns nil for non-LC call numbers. For now, this returns only the call number values from the enrichment
49
+ # inventory fields.
50
+ # @note this could be made more efficient by just returning the first value, but in the future we may want to be
51
+ # more discerning about which call number we return (longest?)
52
+ # @param record [MARC::Record]
53
+ # @return [String, nil] a normalized call number
54
+ def sort(record)
55
+ values(record, loc_only: true).filter_map { |val| Lcsort.normalize(val) }.first
56
+ end
57
+
45
58
  # Parse call number values from inventory fields, including both hld and itm fields from publishing enrichment.
46
59
  # Return only unique values.
47
60
  # @param record [MARC::Record]
48
61
  # @return [Array<String>] array of call numbers from inventory fields
49
62
  def call_number_search(record)
63
+ values(record, loc_only: false)
64
+ end
65
+
66
+ # Return raw call number values from inventory fields
67
+ # @param record [MARC::Record]
68
+ # @param loc_only [Boolean] whether or not to explicitly return only LOC call numbers from ITM & AVA inventory
69
+ # @return [Array<String>]
70
+ def values(record, loc_only:)
50
71
  call_nums = record.fields(TAGS).filter_map do |field|
72
+ next if loc_only && !loc_call_number_type?(subfield_values(field, call_number_type_sf(field))&.first)
73
+
51
74
  subfield_values(field, call_number_sf(field))
52
75
  end
53
76
 
54
- # Ensure we get call numbers for records with no `itm` tags by also checking `hld` and de-duping
55
- call_nums += record.fields([Enriched::Pub::PHYS_INVENTORY_TAG]).filter_map do |field|
77
+ call_nums += hld_field_call_nums(record)
78
+ call_nums.flatten.uniq
79
+ end
80
+
81
+ private
82
+
83
+ # Get call nums from `hld` tags. Useful if no call nums are available from `itm` tags
84
+ # @param record [MARC::Record]
85
+ # @return [Array<String>]
86
+ def hld_field_call_nums(record)
87
+ record.fields([Enriched::Pub::PHYS_INVENTORY_TAG]).filter_map do |field|
56
88
  first = subfield_values(field, Enriched::Pub::HOLDING_CLASSIFICATION_PART)&.first
57
89
  last = subfield_values(field, Enriched::Pub::HOLDING_ITEM_PART)&.first
58
90
  "#{first} #{last}".squish.presence
59
91
  end
60
- call_nums.flatten.uniq
61
92
  end
62
93
 
63
- private
64
-
65
94
  # Retrieve subfield code that stores the call number on enriched marc field
66
95
  # @param field [MARC::DataField]
67
96
  # @return [String]
@@ -106,10 +135,10 @@ module PennMARC
106
135
  end
107
136
 
108
137
  # Determine whether call number type is library of congress
109
- # @param call_number_type [String] value from call number type subfield
138
+ # @param call_number_type [String, nil] value from call number type subfield
110
139
  # @return [Boolean]
111
140
  def loc_call_number_type?(call_number_type)
112
- call_number_type == '0'
141
+ call_number_type == LOC_CALL_NUMBER_TYPE
113
142
  end
114
143
  end
115
144
  end
@@ -21,6 +21,16 @@ module PennMARC
21
21
  }.uniq
22
22
  end
23
23
 
24
+ # Get "Contained in" values from {https://www.oclc.org/bibformats/en/7xx/773.html MARC 773}
25
+ # subfield g for related parts.
26
+ # @param record [MARC::Record]
27
+ # @return [Array<String>] related parts values for display
28
+ def contained_in_related_parts_show(record)
29
+ record.fields('773').map { |field|
30
+ join_subfields(field, &subfield_in?(%w[g]))
31
+ }.uniq
32
+ end
33
+
24
34
  # Get "chronology" information from specially-prefixed 650 (subject) fields
25
35
  # @todo why do we stuff chronology data in a 650 field?
26
36
  # @param record [MARC::Record]
@@ -840,6 +840,10 @@ mideastres:
840
840
  - Van Pelt-Dietrich Library Center
841
841
  - Reserve
842
842
  display: Van Pelt - Middle East Seminar Reserve
843
+ mscirc:
844
+ specific_location: Penn Museum Library - Circulation
845
+ library: Penn Museum Library
846
+ display: Penn Museum Library - Circulation
843
847
  muse:
844
848
  specific_location: Penn Museum Library
845
849
  library: Penn Museum Library
@@ -867,6 +871,14 @@ museegypov:
867
871
  specific_location: Penn Museum Library - Egyptian Oversize
868
872
  library: Penn Museum Library
869
873
  display: Penn Museum Library - Egyptian Oversize
874
+ museexhi:
875
+ specific_location: Penn Museum Library - Exhibition
876
+ library: Penn Museum Library
877
+ display: Penn Museum Library - Exhibition
878
+ musefolio:
879
+ specific_location: Penn Museum Library - Folio
880
+ library: Penn Museum Library
881
+ display: Penn Museum Library - Folio
870
882
  musekolb:
871
883
  specific_location: Penn Museum Library - Kolb
872
884
  library: Penn Museum Library
@@ -882,10 +894,14 @@ musemedia:
882
894
  specific_location: Penn Museum Library - Media
883
895
  library: Penn Museum Library
884
896
  display: Penn Museum Library - Media
885
- musenewbk:
897
+ musenewbks:
886
898
  specific_location: Penn Museum Library - New Books Display
887
899
  library: Penn Museum Library
888
900
  display: Penn Museum Library - New Books Display
901
+ museover:
902
+ specific_location: Penn Museum Library - Oversize
903
+ library: Penn Museum Library
904
+ display: Penn Museum Library - Oversize
889
905
  muserefe:
890
906
  specific_location: Penn Museum Library - Reference
891
907
  library: Penn Museum Library
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PennMARC
4
- VERSION = '1.2.4'
4
+ VERSION = '1.2.6'
5
5
  end
data/pennmarc.gemspec CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.required_ruby_version = '>= 3.2'
20
20
 
21
21
  s.add_dependency 'activesupport', '~> 7'
22
+ s.add_dependency 'lcsort', '~> 0.9'
22
23
  s.add_dependency 'library_stdnums', '~> 1.6'
23
24
  s.add_dependency 'marc', '~> 1.2'
24
25
  s.add_dependency 'nokogiri', '~> 1.15'
@@ -43,6 +43,86 @@ describe 'PennMARC::Classification' do
43
43
  end
44
44
  end
45
45
 
46
+ describe '.sort' do
47
+ context 'with enrichment via the Alma publishing process and no valid call number' do
48
+ let(:fields) do
49
+ [marc_field(tag: PennMARC::Enriched::Pub::ITEM_TAG, subfields: {
50
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE => helper::LOC_CALL_NUMBER_TYPE,
51
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER => 'Secret Drawer Copy'
52
+ }),
53
+ marc_field(tag: PennMARC::Enriched::Pub::ITEM_TAG, subfields: {
54
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE => helper::DEWEY_CALL_NUMBER_TYPE,
55
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER => '691.3 B2141'
56
+ })]
57
+ end
58
+
59
+ it 'returns nil' do
60
+ expect(helper.sort(record)).to be_nil
61
+ end
62
+ end
63
+
64
+ context 'with enrichment via the Alma publishing process and itm fields' do
65
+ let(:fields) do
66
+ [marc_field(tag: PennMARC::Enriched::Pub::ITEM_TAG, subfields: {
67
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE => helper::LOC_CALL_NUMBER_TYPE,
68
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER => 'QL756 .S643'
69
+ }),
70
+ marc_field(tag: PennMARC::Enriched::Pub::ITEM_TAG, subfields: {
71
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE => helper::LOC_CALL_NUMBER_TYPE,
72
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER => 'Secret Drawer Copy'
73
+ }),
74
+ marc_field(tag: PennMARC::Enriched::Pub::ITEM_TAG, subfields: {
75
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE => helper::DEWEY_CALL_NUMBER_TYPE,
76
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER => '691.3 B2141'
77
+ })]
78
+ end
79
+
80
+ it 'returns a normalized version of the first valid call number' do
81
+ expect(helper.sort(record)).to eq 'QL.0756.S643'
82
+ end
83
+ end
84
+
85
+ context 'with enrichment via the Alma publishing process including both hld and itm fields' do
86
+ let(:fields) do
87
+ [marc_field(tag: PennMARC::Enriched::Pub::ITEM_TAG,
88
+ subfields: { PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE => helper::LOC_CALL_NUMBER_TYPE,
89
+ PennMARC::Enriched::Pub::ITEM_CALL_NUMBER => 'QL756 .S643 1989' }),
90
+ marc_field(tag: PennMARC::Enriched::Pub::PHYS_INVENTORY_TAG,
91
+ subfields: { PennMARC::Enriched::Pub::HOLDING_CLASSIFICATION_PART => 'QL756',
92
+ PennMARC::Enriched::Pub::HOLDING_ITEM_PART => '.S643' })]
93
+ end
94
+
95
+ it 'returns a normalized version of the first valid call number' do
96
+ expect(helper.sort(record)).to eq 'QL.0756.S643.1989'
97
+ end
98
+ end
99
+
100
+ context 'with enrichment via the Alma publishing process and only hld fields' do
101
+ let(:fields) do
102
+ [marc_field(tag: PennMARC::Enriched::Pub::PHYS_INVENTORY_TAG,
103
+ subfields: { PennMARC::Enriched::Pub::HOLDING_CLASSIFICATION_PART => 'KF6450',
104
+ PennMARC::Enriched::Pub::HOLDING_ITEM_PART => '.C59 1989' })]
105
+ end
106
+
107
+ it 'returns expected normalized value' do
108
+ expect(helper.sort(record)).to eq 'KF.6450.C59.1989'
109
+ end
110
+ end
111
+
112
+ context 'with enrichment with availability info via Alma Api' do
113
+ let(:fields) do
114
+ [marc_field(tag: PennMARC::Enriched::Api::PHYS_INVENTORY_TAG, subfields: {
115
+ PennMARC::Enriched::Api::PHYS_CALL_NUMBER_TYPE => helper::LOC_CALL_NUMBER_TYPE,
116
+ PennMARC::Enriched::Api::PHYS_CALL_NUMBER => 'QL756 .S643'
117
+ })]
118
+ end
119
+
120
+ it 'returns expected normalized value' do
121
+ expect(helper.sort(record)).to eq 'QL.0756.S643'
122
+ end
123
+ end
124
+ end
125
+
46
126
  describe '.call_number_search' do
47
127
  let(:fields) do
48
128
  [marc_field(tag: config[:tag],
@@ -16,6 +16,16 @@ describe 'PennMARC::Relation' do
16
16
  end
17
17
  end
18
18
 
19
+ describe '.contained_in_related_parts_show' do
20
+ let(:fields) do
21
+ [marc_field(tag: '773', subfields: { g: 'Vol. 24, pt. B no. 9 (Sept. 1993)', q: '24:B:9<235' })]
22
+ end
23
+
24
+ it 'returns only the specified subfields' do
25
+ expect(helper.contained_in_related_parts_show(record)).to eq ['Vol. 24, pt. B no. 9 (Sept. 1993)']
26
+ end
27
+ end
28
+
19
29
  describe '.chronology_show' do
20
30
  let(:fields) do
21
31
  [marc_field(tag: '650', indicator2: '4', subfields: { a: 'CHR Heading' }),
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.2.4
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Kanning
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2024-12-03 00:00:00.000000000 Z
15
+ date: 2024-12-20 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: activesupport
@@ -28,6 +28,20 @@ dependencies:
28
28
  - - "~>"
29
29
  - !ruby/object:Gem::Version
30
30
  version: '7'
31
+ - !ruby/object:Gem::Dependency
32
+ name: lcsort
33
+ requirement: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - "~>"
36
+ - !ruby/object:Gem::Version
37
+ version: '0.9'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - "~>"
43
+ - !ruby/object:Gem::Version
44
+ version: '0.9'
31
45
  - !ruby/object:Gem::Dependency
32
46
  name: library_stdnums
33
47
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +95,7 @@ files:
81
95
  - ".gitignore"
82
96
  - ".gitlab-ci.yml"
83
97
  - ".gitleaks.toml"
98
+ - ".pipeline.yml"
84
99
  - ".rspec"
85
100
  - ".rubocop.yml"
86
101
  - ".rubocop_todo.yml"