cocina_display 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68f245cc22043da571e4a9abf0f0fedd613fc19035c4a50369b95400c76573b5
4
- data.tar.gz: 86de10bbfcabc44d23d9b3df480eb6b3f0d28b358c9f4fece1ece089cf5cca20
3
+ metadata.gz: e2b164eda474dc396b74c8b911fe50bbba7c2dbbce17b29c24923db4f25ff8a5
4
+ data.tar.gz: c782186b53a4dcb7f53b11a3d552dcf5f892f4435fc46fb4bcddd65ad444d9be
5
5
  SHA512:
6
- metadata.gz: 9ea9ac44480ac83533704c06705f0f6f078ac4065b27756cea527f4110ab99b3bafcfcf525097368005e89534bed78a8bb17a81cb96dc1c48c479a5052b877f6
7
- data.tar.gz: 3600cc57ec5e162cb39e2547fa09c9dc7aadc87742146649a0c9d3427ad6c8a2eede1357103af6d4cab531e51d93136a69f075de0995c86daaf471fac2ff08c7
6
+ metadata.gz: efed0c780617aaeadb7187391c74691bcacddcb24cb2850e14918ab755acfaef273c8e3277a9c4674b2b41201a72ffe91a79a0065daa07ad469e2178ea17fd92
7
+ data.tar.gz: 28933bab6be3f6cd2fed4155d5b4a73bf9f9d9301e5edc679018f6428d90f88ca14ab8a6614d1c07e53f1723644b0871786fb075b664bb7e2fb73dba96d21588
@@ -16,6 +16,7 @@ require_relative "concerns/subjects"
16
16
  require_relative "concerns/forms"
17
17
  require_relative "concerns/languages"
18
18
  require_relative "concerns/geospatial"
19
+ require_relative "concerns/structural"
19
20
  require_relative "utils"
20
21
 
21
22
  module CocinaDisplay
@@ -30,6 +31,7 @@ module CocinaDisplay
30
31
  include CocinaDisplay::Concerns::Forms
31
32
  include CocinaDisplay::Concerns::Languages
32
33
  include CocinaDisplay::Concerns::Geospatial
34
+ include CocinaDisplay::Concerns::Structural
33
35
 
34
36
  # Fetch a public Cocina document from PURL and create a CocinaRecord.
35
37
  # @note This is intended to be used in development or testing only.
@@ -88,12 +90,13 @@ module CocinaDisplay
88
90
  end
89
91
 
90
92
  # SDR content type of the object.
91
- # @return [String]
93
+ # @note {RelatedResource}s may not have a content type.
94
+ # @return [String, nil]
92
95
  # @see https://github.com/sul-dlss/cocina-models/blob/main/openapi.yml#L532-L546
93
96
  # @example
94
97
  # record.content_type #=> "image"
95
98
  def content_type
96
- cocina_doc["type"].split("/").last
99
+ cocina_doc["type"]&.split("/")&.last
97
100
  end
98
101
 
99
102
  # Primary processing label for the object.
@@ -109,16 +112,27 @@ module CocinaDisplay
109
112
  content_type == "collection"
110
113
  end
111
114
 
112
- # Traverse nested FileSets and return an enumerator over their files.
113
- # Each file is a +Hash+.
114
- # @return [Enumerator] Enumerator over file hashes
115
- # @example
116
- # record.files.each do |file|
117
- # puts file["filename"] #=> "image1.jpg"
118
- # puts file["size"] #=> 123456
119
- # end
120
- def files
121
- path("$.structural.contains.*.structural.contains[*]")
115
+ # Resources related to the object.
116
+ # @return [Array<CocinaDisplay::RelatedResource>]
117
+ def related_resources
118
+ @related_resources ||= path("$.description.relatedResource[*]").map { |res| RelatedResource.new(res) }
119
+ end
120
+ end
121
+
122
+ # A resource related to the record; behaves like a CocinaRecord.
123
+ # @note Related resources have no structural metadata.
124
+ class RelatedResource < CocinaRecord
125
+ # Description of the relation to the source record.
126
+ # @return [String]
127
+ # @example "is part of"
128
+ # @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#relatedresource-types
129
+ attr_reader :type
130
+
131
+ # Restructure the hash so that everything is under "description" key, since
132
+ # it's all descriptive metadata. This makes most CocinaRecord methods work.
133
+ def initialize(cocina_doc)
134
+ @type = cocina_doc["type"]
135
+ @cocina_doc = {"description" => cocina_doc.except("type")}
122
136
  end
123
137
  end
124
138
  end
@@ -7,30 +7,7 @@ module CocinaDisplay
7
7
  # @example
8
8
  # record.purl_url #=> "https://purl.stanford.edu/bx658jh7339"
9
9
  def purl_url
10
- cocina_doc.dig("description", "purl") || "https://purl.stanford.edu/#{bare_druid}"
11
- end
12
-
13
- # The URL to the PURL environment this object is from.
14
- # @note Objects accessed via UAT will still have a production PURL base URL.
15
- # @return [String]
16
- # @example
17
- # record.purl_base_url #=> "https://purl.stanford.edu"
18
- def purl_base_url
19
- URI(purl_url).origin
20
- end
21
-
22
- # The URL to the stacks environment this object is shelved in.
23
- # Corresponds to the PURL environment.
24
- # @see purl_base_url
25
- # @return [String]
26
- # @example
27
- # record.stacks_base_url #=> "https://stacks.stanford.edu"
28
- def stacks_base_url
29
- if purl_base_url == "https://sul-purl-stage.stanford.edu"
30
- "https://sul-stacks-stage.stanford.edu"
31
- else
32
- "https://stacks.stanford.edu"
33
- end
10
+ cocina_doc.dig("description", "purl") || ("https://purl.stanford.edu/#{bare_druid}" if bare_druid)
34
11
  end
35
12
 
36
13
  # The oEmbed URL for the object, optionally with additional parameters.
@@ -41,7 +18,7 @@ module CocinaDisplay
41
18
  # @example Generate an oEmbed URL for the viewer and hide the title
42
19
  # record.oembed_url(hide_title: true) #=> "https://purl.stanford.edu/bx658jh7339/embed.json?hide_title=true"
43
20
  def oembed_url(params: {})
44
- return if collection?
21
+ return if collection? || purl_url.blank?
45
22
 
46
23
  params[:url] ||= purl_url
47
24
  "#{purl_base_url}/embed.json?#{params.to_query}"
@@ -53,7 +30,7 @@ module CocinaDisplay
53
30
  # @example
54
31
  # record.download_url #=> "https://stacks.stanford.edu/object/bx658jh7339"
55
32
  def download_url
56
- "#{stacks_base_url}/object/#{bare_druid}"
33
+ "#{stacks_base_url}/object/#{bare_druid}" if bare_druid.present?
57
34
  end
58
35
 
59
36
  # The IIIF manifest URL for the object.
@@ -64,7 +41,32 @@ module CocinaDisplay
64
41
  # record.iiif_manifest_url #=> "https://purl.stanford.edu/bx658jh7339/iiif3/manifest"
65
42
  def iiif_manifest_url(version: 3)
66
43
  iiif_path = (version == 3) ? "iiif3" : "iiif"
67
- "#{purl_url}/#{iiif_path}/manifest"
44
+ "#{purl_url}/#{iiif_path}/manifest" if purl_url.present?
45
+ end
46
+
47
+ private
48
+
49
+ # The URL to the PURL environment this object is from.
50
+ # @note Objects accessed via UAT will still have a production PURL base URL.
51
+ # @return [String]
52
+ # @example
53
+ # record.purl_base_url #=> "https://purl.stanford.edu"
54
+ def purl_base_url
55
+ URI(purl_url).origin if purl_url.present?
56
+ end
57
+
58
+ # The URL to the stacks environment this object is shelved in.
59
+ # Corresponds to the PURL environment.
60
+ # @see purl_base_url
61
+ # @return [String]
62
+ # @example
63
+ # record.stacks_base_url #=> "https://stacks.stanford.edu"
64
+ def stacks_base_url
65
+ if purl_base_url == "https://sul-purl-stage.stanford.edu"
66
+ "https://sul-stacks-stage.stanford.edu"
67
+ elsif purl_base_url.present?
68
+ "https://stacks.stanford.edu"
69
+ end
68
70
  end
69
71
  end
70
72
  end
@@ -3,19 +3,22 @@ module CocinaDisplay
3
3
  # Methods for extracting and formatting identifiers from Cocina records.
4
4
  module Identifiers
5
5
  # The DRUID for the object, with the +druid:+ prefix.
6
- # @return [String]
6
+ # @note A {RelatedResource} may not have a DRUID.
7
+ # @return [String, nil]
7
8
  # @example
8
9
  # record.druid #=> "druid:bb099mt5053"
9
10
  def druid
10
- cocina_doc["externalIdentifier"]
11
+ cocina_doc["externalIdentifier"] ||
12
+ cocina_doc.dig("description", "purl")&.split("/")&.last
11
13
  end
12
14
 
13
15
  # The DRUID for the object, without the +druid:+ prefix.
14
- # @return [String]
16
+ # @note A {RelatedResource} may not have a DRUID.
17
+ # @return [String, nil]
15
18
  # @example
16
19
  # record.bare_druid #=> "bb099mt5053"
17
20
  def bare_druid
18
- druid.delete_prefix("druid:")
21
+ druid&.delete_prefix("druid:")
19
22
  end
20
23
 
21
24
  # The DOI for the object, if there is one – just the identifier part.
@@ -59,7 +62,7 @@ module CocinaDisplay
59
62
  # @note This doesn't imply the object is available in Searchworks at this ID.
60
63
  # @see folio_hrid
61
64
  # @see bare_druid
62
- # @return [String]
65
+ # @return [String, nil]
63
66
  def searchworks_id
64
67
  folio_hrid || bare_druid
65
68
  end
@@ -0,0 +1,41 @@
1
+ require "active_support/number_helper/number_to_human_size_converter"
2
+
3
+ module CocinaDisplay
4
+ module Concerns
5
+ # Methods for inspecting structural metadata (e.g. file hierarchy)
6
+ module Structural
7
+ # Structured data for all individual files in the object.
8
+ # Traverses nested FileSet structure to return a flattened array.
9
+ # @return [Array<Hash>]
10
+ # @example
11
+ # record.files.each do |file|
12
+ # puts file["filename"] #=> "image1.jpg"
13
+ # puts file["size"] #=> 123456
14
+ # end
15
+ def files
16
+ @files ||= path("$.structural.contains.*.structural.contains.*").search
17
+ end
18
+
19
+ # All unique MIME types of files in this object.
20
+ # @return [Array<String>]
21
+ # @example ["image/jpeg", "application/pdf"]
22
+ def file_mime_types
23
+ files.pluck("hasMimeType").uniq
24
+ end
25
+
26
+ # Human-readable string representation of {total_file_size_int}.
27
+ # @return [String]
28
+ # @example "2.5 MB"
29
+ def total_file_size_str
30
+ ActiveSupport::NumberHelper.number_to_human_size(total_file_size_int)
31
+ end
32
+
33
+ # Summed size of all files in bytes.
34
+ # @return [Integer]
35
+ # @example 2621440
36
+ def total_file_size_int
37
+ files.pluck("size").sum
38
+ end
39
+ end
40
+ end
41
+ end
@@ -63,6 +63,12 @@ module CocinaDisplay
63
63
  roles.any?(&:publisher?)
64
64
  end
65
65
 
66
+ # Does this contributor have a role that indicates they are a funder?
67
+ # @return [Boolean]
68
+ def funder?
69
+ roles.any?(&:funder?)
70
+ end
71
+
66
72
  # Does this contributor have any roles defined?
67
73
  # @return [Boolean]
68
74
  def role?
@@ -77,6 +83,20 @@ module CocinaDisplay
77
83
  names.map { |name| name.to_s(with_date: with_date) }.first
78
84
  end
79
85
 
86
+ # The full forename for the contributor from the first available name.
87
+ # @see Contributor::Name::forename_str
88
+ # @return [String, nil]
89
+ def forename
90
+ names.map(&:forename_str).first.presence
91
+ end
92
+
93
+ # The full surname for the contributor from the first available name.
94
+ # @see Contributor::Name::surname_str
95
+ # @return [String, nil]
96
+ def surname
97
+ names.map(&:surname_str).first.presence
98
+ end
99
+
80
100
  # A string representation of the contributor's roles, formatted for display.
81
101
  # If there are multiple roles, they are joined with commas.
82
102
  # @return [String]
@@ -37,8 +37,6 @@ module CocinaDisplay
37
37
  end
38
38
  end
39
39
 
40
- private
41
-
42
40
  # The full name as a string, combining all name components and terms of address.
43
41
  # @return [String]
44
42
  def full_name_str
@@ -56,33 +54,35 @@ module CocinaDisplay
56
54
  # Otherwise, fall back to any names explicitly marked as "name" or untyped.
57
55
  # @return [Array<String>]
58
56
  def name_components
59
- [surname_str, forename_ordinal_str].compact_blank.presence || Array(name_values["name"])
57
+ [surname_str, forename_str].compact_blank.presence || Array(name_values["name"])
60
58
  end
61
59
 
62
- # Flatten all forenames and ordinals into a single string.
60
+ # Flatten all terms of address into a comma-delimited string.
63
61
  # @return [String]
64
- def forename_ordinal_str
65
- Utils.compact_and_join(Array(name_values["forename"]) + Array(name_values["ordinal"]), delimiter: " ")
62
+ def terms_of_address_str
63
+ Utils.compact_and_join(Array(name_values["term of address"]), delimiter: ", ")
66
64
  end
67
65
 
68
- # Flatten all terms of address into a single string.
66
+ # Flatten all forename values and ordinals into a whitespace-delimited string.
69
67
  # @return [String]
70
- def terms_of_address_str
71
- Utils.compact_and_join(Array(name_values["term of address"]), delimiter: ", ")
68
+ def forename_str
69
+ Utils.compact_and_join(Array(name_values["forename"]) + Array(name_values["ordinal"]), delimiter: " ")
72
70
  end
73
71
 
74
- # Flatten all surnames into a single string.
72
+ # Flatten all surname values into a whitespace-delimited string.
75
73
  # @return [String]
76
74
  def surname_str
77
75
  Utils.compact_and_join(Array(name_values["surname"]), delimiter: " ")
78
76
  end
79
77
 
80
- # Flatten all life and activity dates into a single string.
78
+ # Flatten all life and activity dates into a comma-delimited string.
81
79
  # @return [String]
82
80
  def dates_str
83
81
  Utils.compact_and_join(Array(name_values["life dates"]) + Array(name_values["activity dates"]), delimiter: ", ")
84
82
  end
85
83
 
84
+ private
85
+
86
86
  # A hash mapping destructured name types to their values.
87
87
  # Name values with no type are grouped under "name".
88
88
  # @return [Hash<String, Array<String>>]
@@ -30,7 +30,7 @@ module CocinaDisplay
30
30
  # Does this role indicate the contributor is an author?
31
31
  # @return [Boolean]
32
32
  def author?
33
- to_s =~ /^(author|creator)/i
33
+ to_s =~ /^(author|creator|primary investigator)/i
34
34
  end
35
35
 
36
36
  # Does this role indicate the contributor is a publisher?
@@ -39,6 +39,12 @@ module CocinaDisplay
39
39
  to_s =~ /^publisher/i
40
40
  end
41
41
 
42
+ # Does this role indicate the contributor is a funder?
43
+ # @return [Boolean]
44
+ def funder?
45
+ to_s =~ /^funder/i
46
+ end
47
+
42
48
  private
43
49
 
44
50
  # Does this role have a MARC relator code?
@@ -99,6 +99,8 @@ module CocinaDisplay
99
99
  # this may be useful for boosting and exact matching for search results
100
100
  # @return [Array<String>] the main title value(s) for Solr - can be array due to parallel titles
101
101
  def main_title(titles)
102
+ return [] if titles.empty?
103
+
102
104
  cocina_title = primary_title(titles) || untyped_title(titles)
103
105
  cocina_title = other_title(titles) if cocina_title.blank?
104
106
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # :nodoc:
4
4
  module CocinaDisplay
5
- VERSION = "1.0.0" # :nodoc:
5
+ VERSION = "1.1.1" # :nodoc:
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocina_display
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Budak
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-08 00:00:00.000000000 Z
11
+ date: 2025-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: janeway-jsonpath
@@ -219,6 +219,7 @@ files:
219
219
  - lib/cocina_display/concerns/geospatial.rb
220
220
  - lib/cocina_display/concerns/identifiers.rb
221
221
  - lib/cocina_display/concerns/languages.rb
222
+ - lib/cocina_display/concerns/structural.rb
222
223
  - lib/cocina_display/concerns/subjects.rb
223
224
  - lib/cocina_display/concerns/titles.rb
224
225
  - lib/cocina_display/contributors/contributor.rb