cocina_display 0.4.0 → 0.5.0

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: 0724f307088c9cdd9348af463e3c659811eef65d0f1f04e094b4c664a36cfed1
4
- data.tar.gz: 4b49472d07a085455ca72204e6ebde3cfc27c634f8ba9cd5b2c2a47d62d19aad
3
+ metadata.gz: 5662a1512975323d0324c31021fcef9cd4ff337ea8acfb5f6a3352d4abf0e303
4
+ data.tar.gz: fe0444d5c104e393718a996260346f98691f2e84585483bb7a2062acab347bfb
5
5
  SHA512:
6
- metadata.gz: edc1e8977792d21fd6fef292f486c11eab4011dc63c854af1217aced6930ea55538e158abc261491c08b1f8492c374c52102b44931849b2350e1dacf54310d41
7
- data.tar.gz: 664691764d4a4ff7da024af64ce9dd2d0841d8c014ee9439c3ebebe4b3a4c2e585e7f608f2ff943a04586dfaad8fc4ec7cfa714fade929ca9b0278ec740bff8b
6
+ metadata.gz: 8ff64dadfe8b2492f23d38bcc320e8de4bc6100af09adfd93d25df0aeb84c91585072dbcbb6cafc297bd503d773ee7b6bf35281fdda1d573e65f94fcb866a5a0
7
+ data.tar.gz: 29c42ee65d59dbac907159435f8c9924742cc4d598e656a787dee8cbfceaab8231f2bbf8cb87d14ee180ed586fc51b4d498e8009b6fd5442442d8b0932ebd30d
@@ -11,6 +11,8 @@ require_relative "concerns/events"
11
11
  require_relative "concerns/contributors"
12
12
  require_relative "concerns/identifiers"
13
13
  require_relative "concerns/titles"
14
+ require_relative "concerns/access"
15
+ require_relative "concerns/subjects"
14
16
 
15
17
  module CocinaDisplay
16
18
  # Public Cocina metadata for an SDR object, as fetched from PURL.
@@ -19,6 +21,8 @@ module CocinaDisplay
19
21
  include CocinaDisplay::Concerns::Contributors
20
22
  include CocinaDisplay::Concerns::Identifiers
21
23
  include CocinaDisplay::Concerns::Titles
24
+ include CocinaDisplay::Concerns::Access
25
+ include CocinaDisplay::Concerns::Subjects
22
26
 
23
27
  # Fetch a public Cocina document from PURL and create a CocinaRecord.
24
28
  # @note This is intended to be used in development or testing only.
@@ -73,6 +77,13 @@ module CocinaDisplay
73
77
  cocina_doc["type"].split("/").last
74
78
  end
75
79
 
80
+ # Primary processing label for the object.
81
+ # @note This may or may not be the same as the title.
82
+ # @return [String, nil]
83
+ def label
84
+ cocina_doc["label"]
85
+ end
86
+
76
87
  # True if the object is a collection.
77
88
  # @return [Boolean]
78
89
  def collection?
@@ -90,70 +101,5 @@ module CocinaDisplay
90
101
  def files
91
102
  path("$.structural.contains[*].structural.contains[*]")
92
103
  end
93
-
94
- # The PURL URL for this object.
95
- # @return [String]
96
- # @example
97
- # record.purl_url #=> "https://purl.stanford.edu/bx658jh7339"
98
- def purl_url
99
- cocina_doc.dig("description", "purl") || "https://purl.stanford.edu/#{bare_druid}"
100
- end
101
-
102
- # The URL to the PURL environment this object is from.
103
- # @note Objects accessed via UAT will still have a production PURL base URL.
104
- # @return [String]
105
- # @example
106
- # record.purl_base_url #=> "https://purl.stanford.edu"
107
- def purl_base_url
108
- URI(purl_url).origin
109
- end
110
-
111
- # The URL to the stacks environment this object is shelved in.
112
- # Corresponds to the PURL environment.
113
- # @see purl_base_url
114
- # @return [String]
115
- # @example
116
- # record.stacks_base_url #=> "https://stacks.stanford.edu"
117
- def stacks_base_url
118
- if purl_base_url == "https://sul-purl-stage.stanford.edu"
119
- "https://sul-stacks-stage.stanford.edu"
120
- else
121
- "https://stacks.stanford.edu"
122
- end
123
- end
124
-
125
- # The oEmbed URL for the object, optionally with additional parameters.
126
- # Corresponds to the PURL environment.
127
- # @param params [Hash] Additional parameters to include in the oEmbed URL.
128
- # @return [String]
129
- # @return [nil] if the object is a collection.
130
- # @example Generate an oEmbed URL for the viewer and hide the title
131
- # record.oembed_url(hide_title: true) #=> "https://purl.stanford.edu/bx658jh7339/embed.json?hide_title=true"
132
- def oembed_url(params: {})
133
- return if collection?
134
-
135
- params[:url] ||= purl_url
136
- "#{purl_base_url}/embed.json?#{params.to_query}"
137
- end
138
-
139
- # The download URL to get the entire object as a .zip file.
140
- # Stacks generates the .zip for the object on request.
141
- # @return [String]
142
- # @example
143
- # record.download_url #=> "https://stacks.stanford.edu/object/bx658jh7339"
144
- def download_url
145
- "#{stacks_base_url}/object/#{bare_druid}"
146
- end
147
-
148
- # The IIIF manifest URL for the object.
149
- # PURL generates the IIIF manifest.
150
- # @param version [Integer] The IIIF presentation spec version to use (3 or 2).
151
- # @return [String]
152
- # @example
153
- # record.iiif_manifest_url #=> "https://purl.stanford.edu/bx658jh7339/iiif3/manifest"
154
- def iiif_manifest_url(version: 3)
155
- iiif_path = (version == 3) ? "iiif3" : "iiif"
156
- "#{purl_url}/#{iiif_path}/manifest"
157
- end
158
104
  end
159
105
  end
@@ -0,0 +1,71 @@
1
+ module CocinaDisplay
2
+ module Concerns
3
+ # Methods that generate URLs to access an object.
4
+ module Access
5
+ # The PURL URL for this object.
6
+ # @return [String]
7
+ # @example
8
+ # record.purl_url #=> "https://purl.stanford.edu/bx658jh7339"
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
34
+ end
35
+
36
+ # The oEmbed URL for the object, optionally with additional parameters.
37
+ # Corresponds to the PURL environment.
38
+ # @param params [Hash] Additional parameters to include in the oEmbed URL.
39
+ # @return [String]
40
+ # @return [nil] if the object is a collection.
41
+ # @example Generate an oEmbed URL for the viewer and hide the title
42
+ # record.oembed_url(hide_title: true) #=> "https://purl.stanford.edu/bx658jh7339/embed.json?hide_title=true"
43
+ def oembed_url(params: {})
44
+ return if collection?
45
+
46
+ params[:url] ||= purl_url
47
+ "#{purl_base_url}/embed.json?#{params.to_query}"
48
+ end
49
+
50
+ # The download URL to get the entire object as a .zip file.
51
+ # Stacks generates the .zip for the object on request.
52
+ # @return [String]
53
+ # @example
54
+ # record.download_url #=> "https://stacks.stanford.edu/object/bx658jh7339"
55
+ def download_url
56
+ "#{stacks_base_url}/object/#{bare_druid}"
57
+ end
58
+
59
+ # The IIIF manifest URL for the object.
60
+ # PURL generates the IIIF manifest.
61
+ # @param version [Integer] The IIIF presentation spec version to use (3 or 2).
62
+ # @return [String]
63
+ # @example
64
+ # record.iiif_manifest_url #=> "https://purl.stanford.edu/bx658jh7339/iiif3/manifest"
65
+ def iiif_manifest_url(version: 3)
66
+ iiif_path = (version == 3) ? "iiif3" : "iiif"
67
+ "#{purl_url}/#{iiif_path}/manifest"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -41,6 +41,20 @@ module CocinaDisplay
41
41
  pub_date_edtf(ignore_qualified: ignore_qualified)&.year
42
42
  end
43
43
 
44
+ # The range of preferred publication years as an array of integers.
45
+ # Considers publication, creation, and capture dates in that order.
46
+ # Prefers dates marked as primary and those with a declared encoding.
47
+ # @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
48
+ # @return [Array<Integer>, nil]
49
+ # @note 6 BCE will appear as -5; 4 CE will appear as 4.
50
+ def pub_year_int_range(ignore_qualified: false)
51
+ date = pub_date(ignore_qualified: ignore_qualified)
52
+ return unless date
53
+
54
+ date = date.as_interval if date.is_a? CocinaDisplay::Dates::DateRange
55
+ date.to_a.map(&:year).compact.uniq.sort
56
+ end
57
+
44
58
  # String for displaying the earliest preferred publication year or range.
45
59
  # Considers publication, creation, and capture dates in that order.
46
60
  # Prefers dates marked as primary and those with a declared encoding.
@@ -40,11 +40,19 @@ module CocinaDisplay
40
40
 
41
41
  # The HRID of the item in FOLIO, if defined.
42
42
  # @note This doesn't imply the object is available in Searchworks at this ID.
43
+ # @param [refresh] [Boolean] Filter to links with refresh set to this value.
43
44
  # @return [String, nil]
44
- # @example
45
+ # @example With a link regardless of refresh:
45
46
  # record.folio_hrid #=> "a12845814"
46
- def folio_hrid
47
- path("$.identification.catalogLinks[?(@.catalog == 'folio')].catalogRecordId").first
47
+ # @example With a link that is not refreshed:
48
+ # record.folio_hrid(refresh: true) #=> nil
49
+ def folio_hrid(refresh: nil)
50
+ link = path("$.identification.catalogLinks[?(@.catalog == 'folio')]").first
51
+ hrid = link&.dig("catalogRecordId")
52
+ return if hrid.blank?
53
+ return hrid if refresh.nil?
54
+
55
+ (link["refresh"] == refresh) ? hrid : nil
48
56
  end
49
57
 
50
58
  # The FOLIO HRID if defined, otherwise the bare DRUID.
@@ -0,0 +1,58 @@
1
+ require_relative "../subject"
2
+
3
+ module CocinaDisplay
4
+ module Concerns
5
+ # Methods for extracting and formatting subject information.
6
+ module Subjects
7
+ # All unique subjects that are topics, formatted as strings for display.
8
+ # @return [Array<String>]
9
+ def subject_topics
10
+ subjects.filter { |s| s.type == "topic" }.map(&:display_str).uniq
11
+ end
12
+
13
+ # All unique subjects that are genres, formatted as strings for display.
14
+ # @return [Array<String>]
15
+ def subject_genres
16
+ subjects.filter { |s| s.type == "genre" }.map(&:display_str).uniq
17
+ end
18
+
19
+ # All unique subjects that are titles, formatted as strings for display.
20
+ # @return [Array<String>]
21
+ def subject_titles
22
+ subjects.filter { |s| s.type == "title" }.map(&:display_str).uniq
23
+ end
24
+
25
+ # All unique subjects that are date/time info, formatted as strings for display.
26
+ # @return [Array<String>]
27
+ def subject_temporal
28
+ subjects.filter { |s| s.type == "time" }.map(&:display_str).uniq
29
+ end
30
+
31
+ # All unique subjects that are occupations, formatted as strings for display.
32
+ # @return [Array<String>]
33
+ def subject_occupations
34
+ subjects.filter { |s| s.type == "occupation" }.map(&:display_str).uniq
35
+ end
36
+
37
+ # All unique subjects that are names of entities, formatted as strings for display.
38
+ # @note Multiple types are handled: person, family, organization, conference, etc.
39
+ # @see CocinaDisplay::NameSubject
40
+ # @return [Array<String>]
41
+ def subject_names
42
+ subjects.filter { |s| s.is_a? NameSubject }.map(&:display_str).uniq
43
+ end
44
+
45
+ private
46
+
47
+ # All subjects, accessible as Subject objects.
48
+ # Checks both description.subject and description.geographic.subject.
49
+ # @return [Array<Subject>]
50
+ def subjects
51
+ @subjects ||= Enumerator::Chain.new(
52
+ path("$.description.subject[*]"),
53
+ path("$.description.geographic[*].subject[*]")
54
+ ).map { |s| Subject.from_cocina(s) }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -100,6 +100,10 @@ module CocinaDisplay
100
100
  end
101
101
 
102
102
  # The display string for the name, optionally including life dates.
103
+ # Uses these values in order, if present:
104
+ # 1. Unstructured value
105
+ # 2. Any structured/parallel values marked as "display"
106
+ # 3. Joined structured values, optionally with life dates
103
107
  # @param with_date [Boolean] Include life dates, if present
104
108
  # @return [String]
105
109
  # @example no dates
@@ -107,7 +111,11 @@ module CocinaDisplay
107
111
  # @example with dates
108
112
  # name.display_name(with_date: true) # => "King, Martin Luther, Jr., 1929-1968"
109
113
  def display_str(with_date: false)
110
- if dates_str.present? && with_date
114
+ if cocina["value"].present?
115
+ cocina["value"]
116
+ elsif display_name_str.present?
117
+ display_name_str
118
+ elsif dates_str.present? && with_date
111
119
  Utils.compact_and_join([full_name_str, dates_str], delimiter: ", ")
112
120
  else
113
121
  full_name_str
@@ -116,12 +124,10 @@ module CocinaDisplay
116
124
 
117
125
  private
118
126
 
119
- # The full name as a string.
120
- # If any names were marked as "display", prefer those.
121
- # Otherwise, combine all name components.
127
+ # The full name as a string, combining all name components.
122
128
  # @return [String]
123
129
  def full_name_str
124
- display_name_str.presence || Utils.compact_and_join(name_components, delimiter: ", ")
130
+ Utils.compact_and_join(name_components, delimiter: ", ")
125
131
  end
126
132
 
127
133
  # Flattened form of any names explicitly marked as "display name".
@@ -168,7 +174,7 @@ module CocinaDisplay
168
174
  # @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#contributor-name-part-types-for-structured-value
169
175
  # @note Currently we do nothing with "alternative", "inverted full name", "pseudonym", and "transliteration" types.
170
176
  def name_values
171
- Utils.flatten_structured_values(cocina).each_with_object({}) do |node, hash|
177
+ Utils.flatten_nested_values(cocina).each_with_object({}) do |node, hash|
172
178
  type = node["type"] || "name"
173
179
  hash[type] ||= []
174
180
  hash[type] << node["value"]
@@ -0,0 +1,127 @@
1
+ require_relative "utils"
2
+ require_relative "contributor"
3
+ require_relative "title_builder"
4
+ require_relative "dates/date"
5
+
6
+ module CocinaDisplay
7
+ # Base class for subjects in Cocina structured data.
8
+ class Subject
9
+ attr_reader :cocina
10
+
11
+ # Extract the type of the subject from the Cocina structured data.
12
+ # If no top-level type, uses the first structuredValue type.
13
+ # @param cocina [Hash] The Cocina structured data for the subject.
14
+ # @return [String, nil] The type of the subject, or nil if none
15
+ # @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#subject-types
16
+ def self.detect_type(cocina)
17
+ cocina["type"] || Utils.flatten_nested_values(cocina).pick("type")
18
+ end
19
+
20
+ # Choose and create the appropriate Subject subclass based on type.
21
+ # @param cocina [Hash] The Cocina structured data for the subject.
22
+ # @return [Subject]
23
+ # @see detect_type
24
+ def self.from_cocina(cocina)
25
+ case detect_type(cocina)
26
+ when "person", "family", "organization", "conference", "event", "name"
27
+ NameSubject.new(cocina)
28
+ when "title"
29
+ TitleSubject.new(cocina)
30
+ when "time"
31
+ TemporalSubject.new(cocina)
32
+ # TODO: special handling for geospatial subjects
33
+ # when "map coordinates", "bounding box coordinates", "point coordinates"
34
+ else
35
+ Subject.new(cocina)
36
+ end
37
+ end
38
+
39
+ # Initialize a Subject object with Cocina structured data.
40
+ # @param cocina [Hash] The Cocina structured data for the subject.
41
+ def initialize(cocina)
42
+ @cocina = cocina
43
+ end
44
+
45
+ # The type of the subject.
46
+ # If no top-level type, uses the first structuredValue type.
47
+ # @return [String, nil] The type of the subject, or nil if none
48
+ # @see detect_type
49
+ def type
50
+ self.class.detect_type(cocina)
51
+ end
52
+
53
+ # A string representation of the subject, formatted for display.
54
+ # Concatenates any structured values with an appropriate delimiter.
55
+ # Subclasses may override this for more specific formatting.
56
+ # @return [String]
57
+ def display_str
58
+ Utils.compact_and_join(descriptive_values, delimiter: delimiter)
59
+ end
60
+
61
+ private
62
+
63
+ # Flatten any structured values into an array of Hashes with "value" keys.
64
+ # If no structured values, will return the top-level cocina data.
65
+ # @see Utils.flatten_nested_values
66
+ # @return [Array<Hash>] An array of Hashes representing all values.
67
+ def descriptive_values
68
+ Utils.flatten_nested_values(cocina).pluck("value")
69
+ end
70
+
71
+ # Delimiter to use for joining structured subject values.
72
+ # LCSH uses a comma (the default); catalog headings use " > ".
73
+ # @return [String]
74
+ def delimiter
75
+ if cocina["displayLabel"]&.downcase == "catalog heading"
76
+ " > "
77
+ else
78
+ ", "
79
+ end
80
+ end
81
+ end
82
+
83
+ # A subject representing a named entity.
84
+ class NameSubject < Subject
85
+ attr_reader :name
86
+
87
+ # Initialize a NameSubject object with Cocina structured data.
88
+ # @param cocina [Hash] The Cocina structured data for the subject.
89
+ def initialize(cocina)
90
+ super
91
+ @name = Contributor::Name.new(cocina)
92
+ end
93
+
94
+ # Use the contributor name formatting rules for display.
95
+ # @return [String] The formatted name string, including life dates
96
+ # @see CocinaDisplay::Contributor::Name#display_str
97
+ def display_str
98
+ @name.display_str(with_date: true)
99
+ end
100
+ end
101
+
102
+ # A subject representing an entity with a title.
103
+ class TitleSubject < Subject
104
+ # Construct a title string to use for display.
105
+ # @see CocinaDisplay::TitleBuilder.build
106
+ # @note Unclear how often structured title subjects occur "in the wild".
107
+ # @return [String]
108
+ def display_str
109
+ TitleBuilder.build([cocina])
110
+ end
111
+ end
112
+
113
+ # A subject representing a date and/or time.
114
+ class TemporalSubject < Subject
115
+ attr_reader :date
116
+
117
+ def initialize(cocina)
118
+ super
119
+ @date = Dates::Date.from_cocina(cocina)
120
+ end
121
+
122
+ # @return [String] The formatted date/time string for display
123
+ def display_str
124
+ @date.qualified_value
125
+ end
126
+ end
127
+ end
@@ -19,15 +19,31 @@ module CocinaDisplay
19
19
  end.delete_suffix(delimiter)
20
20
  end
21
21
 
22
- # Recursively flatten structured values in Cocina metadata.
23
- # Returns a list of hashes representing the "leaf" nodes with values.
22
+ # Recursively flatten structured and parallel values in Cocina metadata.
23
+ # Returns a list of hashes representing the "leaf" nodes with +value+ key.
24
24
  # @return [Array<Hash>] List of node hashes with "value" present
25
- def self.flatten_structured_values(cocina, output = [])
25
+ # @param cocina [Hash] The Cocina structured data to flatten
26
+ # @param output [Array] Used for recursion, should be empty on first call
27
+ # @example simple value
28
+ # cocina = { "value" => "John Doe", "type" => "name" }
29
+ # Utils.flatten_nested_values(cocina)
30
+ # #=> [{"value" => "John Doe", "type" => "name"}]
31
+ # @example structured values
32
+ # cocina = { "structuredValue" => [{"value" => "foo"}, {"value" => "bar"}] }
33
+ # Utils.flatten_nested_values(cocina)
34
+ # #=> [{"value" => "foo"}, {"value" => "bar"}]
35
+ # @example parallel structured and simple values
36
+ # cocina = { "parallelValue" => [{"value" => "foo" }, { "structuredValue" => [{"value" => "bar"}, {"value" => "baz"}] }] }
37
+ # Utils.flatten_nested_values(cocina)
38
+ # #=> [{"value" => "foo"}, {"value" => "foo"}, {"value" => "baz"}]
39
+ def self.flatten_nested_values(cocina, output = [])
26
40
  return [cocina] if cocina["value"].present?
27
- return cocina.flat_map { |node| flatten_structured_values(node, output) } if cocina.is_a?(Array)
28
- return output unless (structured_values = Array(cocina["structuredValue"])).present?
41
+ return cocina.flat_map { |node| flatten_nested_values(node, output) } if cocina.is_a?(Array)
29
42
 
30
- structured_values.flat_map { |node| flatten_structured_values(node, output) }
43
+ nested_values = Array(cocina["structuredValue"]) + Array(cocina["parallelValue"])
44
+ return output unless nested_values.any?
45
+
46
+ nested_values.flat_map { |node| flatten_nested_values(node, output) }
31
47
  end
32
48
  end
33
49
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # :nodoc:
4
4
  module CocinaDisplay
5
- VERSION = "0.4.0" # :nodoc:
5
+ VERSION = "0.5.0" # :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: 0.4.0
4
+ version: 0.5.0
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-07-09 00:00:00.000000000 Z
11
+ date: 2025-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: janeway-jsonpath
@@ -28,22 +28,16 @@ dependencies:
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '8.0'
34
31
  - - ">="
35
32
  - !ruby/object:Gem::Version
36
- version: 8.0.2
33
+ version: '7'
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
- - - "~>"
42
- - !ruby/object:Gem::Version
43
- version: '8.0'
44
38
  - - ">="
45
39
  - !ruby/object:Gem::Version
46
- version: 8.0.2
40
+ version: '7'
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: edtf
49
43
  requirement: !ruby/object:Gem::Requirement
@@ -176,15 +170,18 @@ files:
176
170
  - Rakefile
177
171
  - lib/cocina_display.rb
178
172
  - lib/cocina_display/cocina_record.rb
173
+ - lib/cocina_display/concerns/access.rb
179
174
  - lib/cocina_display/concerns/contributors.rb
180
175
  - lib/cocina_display/concerns/events.rb
181
176
  - lib/cocina_display/concerns/identifiers.rb
177
+ - lib/cocina_display/concerns/subjects.rb
182
178
  - lib/cocina_display/concerns/titles.rb
183
179
  - lib/cocina_display/contributor.rb
184
180
  - lib/cocina_display/dates/date.rb
185
181
  - lib/cocina_display/dates/date_range.rb
186
182
  - lib/cocina_display/imprint.rb
187
183
  - lib/cocina_display/marc_country_codes.rb
184
+ - lib/cocina_display/subject.rb
188
185
  - lib/cocina_display/title_builder.rb
189
186
  - lib/cocina_display/utils.rb
190
187
  - lib/cocina_display/version.rb