derivative-rodeo 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: bee0908ac5d045db1948b062d3a4e1569fff41b4a5a0f1521f31b620c15c53a6
4
- data.tar.gz: 328de6f1cde3bcdadca31a361821b2c573425d15bfddbea5bf85d9475a8a3d0e
3
+ metadata.gz: f5ea42b2912b6b1eb6981b8a72a3d39e4d6f466e07ad60381ba4880431214b18
4
+ data.tar.gz: 4e4ca5cdd6ba61898ba13970fbd95c8836c33492c377eb6f258255c8ebf79e67
5
5
  SHA512:
6
- metadata.gz: 2d8ba019ef30666d9633b73e90fe599b53d5f6698de0400b43a25c37a06470542962db8f61d74ed6a444f1ecc5ffbd604d2c0891dffcf5fa86d9dce87ace1bb5
7
- data.tar.gz: b682377ce08e4379f1323bfdb6c6b8f46c97d45ebb392bb1dc7f5bc27f5b01701731880dc08f96cb5ef2b0da8ad39349b4534a63b4039e78616331b93bd9516c
6
+ metadata.gz: 6ebaacf09b1459e8bb8527ddd3b45b6e84106d335e91cc506a6b9b31aae6c74931c755dc5e084e9a39a6ddb0ca8f1b0f85ee21e461cac445edd3eea45384b1b5
7
+ data.tar.gz: e11195b3b7169a1f7e2f5df821c681512af93123fc272d3b1dd35d9c971f15ad58a86b4ab1cabf38b463c77d5531114af7ca36eb9197275e46549788a4ec6c1f
@@ -35,7 +35,7 @@ module DerivativeRodeo
35
35
  ##
36
36
  # Raised when AWS bucket does not exist or is not accessible by current permissions
37
37
  class BucketMissingError < Error
38
- def initialize
38
+ def initialize(file_uri:)
39
39
  super("Bucket part missing #{file_uri}")
40
40
  end
41
41
  end
@@ -11,11 +11,21 @@ module DerivativeRodeo
11
11
  ##
12
12
  # The Base Generator defines the interface and common methods.
13
13
  #
14
+ # Fundamentally, they are about ensuring the files end up at the specified location, based on
15
+ # the given:
16
+ #
17
+ # - {#input_uris}
18
+ # - {#output_location_template}
19
+ # - {#preprocessed_location_template}
20
+ #
14
21
  # In extending a BaseGenerator you:
15
22
  #
16
23
  # - must assign an {.output_extension}
17
24
  # - must impliment a {#build_step} method
18
25
  # - may override {#with_each_requisite_location_and_tmp_file_path}
26
+ #
27
+ # {#generated_files} is "where the magic happens"
28
+ # rubocop:disable Metrics/ClassLength
19
29
  class BaseGenerator
20
30
  ##
21
31
  # @!group Class Attributes
@@ -26,9 +36,27 @@ module DerivativeRodeo
26
36
  class_attribute :output_extension
27
37
  # @!endgroup Class Attributes
28
38
 
29
- attr_reader :input_uris,
30
- :output_location_template,
31
- :preprocessed_location_template
39
+ ##
40
+ # @!group Attributes
41
+ #
42
+ # The "original" files that we'll be processing (via {#generated_files})
43
+ # @return [Array<String>]
44
+ attr_reader :input_uris
45
+
46
+ ##
47
+ # The template that defines where we'll be writing the {#input_uris} (via {#generated_files})
48
+ # @return [String]
49
+ # @see DerivativeRodeo::Services::ConvertUriViaTemplateService
50
+ attr_reader :output_location_template
51
+
52
+ ##
53
+ # The template that defines where we might find existing processed files for the given
54
+ # {#input_uris} (via {#generated_files})
55
+ #
56
+ # @return [String, NilClass]
57
+ # @see DerivativeRodeo::Services::ConvertUriViaTemplateService
58
+ attr_reader :preprocessed_location_template
59
+ # @!endgroup Attributes
32
60
 
33
61
  ##
34
62
  # @param input_uris [Array<String>]
@@ -78,14 +106,28 @@ module DerivativeRodeo
78
106
  ##
79
107
  # @api public
80
108
  #
109
+ # Based on the {#input_uris} ensure that we have files at the given output location (as
110
+ # derived from the {#output_location_template}). We ensure that by:
111
+ #
112
+ # - Checking if a file already exists at the output location
113
+ # - Copying a preprocessed file to the output location if a preprocessed file exists
114
+ # - Generating the file based on the input location
115
+ #
116
+ # @note This is the method where the magic happens!
117
+ #
81
118
  # @return [Array<StorageLocations::BaseLocation>]
82
119
  #
83
120
  # @see #build_step
84
121
  # @see #with_each_requisite_location_and_tmp_file_path
122
+ # rubocop:disable Metrics/MethodLength
85
123
  def generated_files
86
124
  # TODO: Examples please
87
125
  return @generated_files if defined?(@generated_files)
88
126
 
127
+ logger.info("Starting #{self.class}#generated_files with " \
128
+ "input_uris: #{input_uris.inspect}, " \
129
+ "output_location_template: #{output_location_template.inspect}, and " \
130
+ "preprocessed_location_template: #{preprocessed_location_template.inspect}.")
89
131
  # As much as I would like to use map or returned values; given the implementations it's
90
132
  # better to explicitly require that; reducing downstream implementation headaches.
91
133
  #
@@ -97,15 +139,20 @@ module DerivativeRodeo
97
139
  # BaseLocation is like the Ruby `File` (Pathname) "File.exist?(path) :: location.exist?"
98
140
  # "file:///Users/jfriesen/.profile"
99
141
  with_each_requisite_location_and_tmp_file_path do |input_location, input_tmp_file_path|
100
- generated_file = destination(input_location)
101
- @generated_files << if generated_file.exist?
102
- generated_file
142
+ output_location = destination(input_location)
143
+ @generated_files << if output_location.exist?
144
+ output_location
103
145
  else
104
- build_step(input_location: input_location, output_location: generated_file, input_tmp_file_path: input_tmp_file_path)
146
+ log_message = "#{self.class}#generated_files :: " \
147
+ "input_location file_uri #{input_location.file_uri} :: " \
148
+ "Generating output_location file_uri #{output_location.file_uri} via build_step."
149
+ logger.info(log_message)
150
+ build_step(input_location: input_location, output_location: output_location, input_tmp_file_path: input_tmp_file_path)
105
151
  end
106
152
  end
107
153
  @generated_files
108
154
  end
155
+ # rubocop:enable Metrics/MethodLength
109
156
 
110
157
  ##
111
158
  # @return [Array<String>]
@@ -157,9 +204,13 @@ module DerivativeRodeo
157
204
  end
158
205
 
159
206
  ##
160
- # Returns the location destination for the given :input_file. The file at the location
161
- # destination might exist or might not. In the case of non-existence, then the {#build_step}
162
- # will create the file.
207
+ # Returns the output location for the given :input_location. The file at the location
208
+ # destination might exist or might not. In the case where we have a
209
+ # {#preprocessed_location_template}, we'll also check the preprocessed location for the file,
210
+ # and if it exists there copy it to the target output location.
211
+ #
212
+ # In the case of non-existence, then the {#build_step} will create
213
+ # the file.
163
214
  #
164
215
  # @param input_location [StorageLocations::BaseLocation]
165
216
  #
@@ -167,20 +218,80 @@ module DerivativeRodeo
167
218
  # {#output_location_template} or {#preprocessed_location_template}.
168
219
  #
169
220
  # @see [StorageLocations::BaseLocation#exist?]
221
+ # rubocop:disable Metrics/MethodLength
222
+ # rubocop:disable Metrics/AbcSize
170
223
  def destination(input_location)
171
- output_location = input_location.derived_file_from(template: output_location_template)
224
+ output_location = input_location.derived_file_from(template: output_location_template, extension: output_extension)
225
+
226
+ if output_location.exist?
227
+ log_message = "#{self.class}#destination :: " \
228
+ "input_location file_uri #{input_location.file_uri} :: " \
229
+ "Found output_location file_uri #{output_location.file_uri}."
230
+ logger.info(log_message)
172
231
 
173
- return output_location if output_location.exist?
174
- return output_location unless preprocessed_location_template
232
+ return output_location
233
+ end
234
+
235
+ unless preprocessed_location_template
236
+ log_message = "#{self.class}#destination :: " \
237
+ "input_location file_uri #{input_location.file_uri} :: " \
238
+ "No preprocessed_location_template provided " \
239
+ "nor does a file exist at output_location file_uri #{output_location.file_uri}; " \
240
+ "moving on to generation via #{self.class}#build_step."
241
+ logger.info(log_message)
242
+
243
+ return output_location
244
+ end
245
+
246
+ template = derive_preprocessed_template_from(input_location: input_location, preprocessed_location_template: preprocessed_location_template)
175
247
 
176
- preprocessed_location = input_location.derived_file_from(template: preprocessed_location_template)
248
+ preprocessed_location = input_location.derived_file_from(template: template, extension: output_extension)
177
249
  # We only want the location if it exists
178
- return preprocessed_location if preprocessed_location&.exist?
250
+ if preprocessed_location.exist?
251
+ log_message = "#{self.class}#destination :: " \
252
+ "input_location file_uri #{input_location.file_uri} :: " \
253
+ "Found preprocessed_location file_uri #{preprocessed_location.file_uri}."
254
+ logger.info(log_message)
255
+
256
+ # Let's make sure we reap the fruits of the pre-processing; and don't worry that generator
257
+ # will also write some logs.
258
+ output_location = CopyGenerator.new(
259
+ input_uris: [preprocessed_location.file_uri],
260
+ output_location_template: output_location.file_uri
261
+ ).generated_files.first
262
+
263
+ return output_location
264
+ end
265
+
266
+ log_message = "#{self.class}#destination :: " \
267
+ "input_location file_uri #{input_location.file_uri} :: " \
268
+ "No file exists at preprocessed_location file_uri #{preprocessed_location.file_uri} " \
269
+ "nor output_location file_uri #{output_location.file_uri}; " \
270
+ "moving on to generation via #{self.class}#build_step."
271
+ logger.info(log_message)
179
272
 
180
273
  # NOTE: The file does not exist at the output_location; but we pass this information along so
181
274
  # that the #build_step knows where to write the file.
182
275
  output_location
183
276
  end
277
+ # rubocop:enable Metrics/AbcSize
278
+ # rubocop:enable Metrics/MethodLength
279
+
280
+ ##
281
+ # Some generators (e.g. {PdfSplitGenerator}) need to cooerce the location template based on
282
+ # the input location. Most often, however, the given :preprocessed_location_template is
283
+ # adequate and would be the typical returned value.
284
+ #
285
+ # @param input_location [StorageLocations::BaseLocation]
286
+ # @param preprocessed_location_template [String]
287
+ #
288
+ # @return [String]
289
+ #
290
+ # rubocop:disable Lint/UnusedMethodArgument
291
+ def derive_preprocessed_template_from(input_location:, preprocessed_location_template:)
292
+ preprocessed_location_template
293
+ end
294
+ # rubocop:enable Lint/UnusedMethodArgument
184
295
 
185
296
  ##
186
297
  # A bit of indirection to create a common interface for running a shell command.
@@ -196,6 +307,7 @@ module DerivativeRodeo
196
307
  result
197
308
  end
198
309
  end
310
+ # rubocop:enable Metrics/ClassLength
199
311
  end
200
312
  end
201
313
 
@@ -5,7 +5,8 @@ module DerivativeRodeo
5
5
  ##
6
6
  # Take images an ensures that we have a monochrome derivative of those images.
7
7
  class MonochromeGenerator < BaseGenerator
8
- # TODO: Can we assume a tiff?
8
+ # @see DerivativeRodeo::Services::ConvertUriViaTemplateService for the interaction of the
9
+ # magic ".mono" suffix
9
10
  self.output_extension = 'mono.tiff'
10
11
 
11
12
  ##
@@ -66,7 +66,7 @@ module DerivativeRodeo
66
66
  #
67
67
  # @note There is relation to {Generators::BaseGenerator#destination} and this method.
68
68
  #
69
- # @note The tail_glob is in relation to the {#image_file_basename_template}
69
+ # @note The tail_regexp is in relation to the {#image_file_basename_template}
70
70
  def existing_page_locations(input_location:)
71
71
  # See image_file_basename_template
72
72
  tail_regexp = %r{#{input_location.file_basename}--page-\d+\.#{output_extension}$}
@@ -76,12 +76,14 @@ module DerivativeRodeo
76
76
 
77
77
  return [] if preprocessed_location_template.blank?
78
78
 
79
- input_location.derived_file_from(template: preprocessed_location_template).globbed_tail_loations(tail_regexp: tail_regexp)
79
+ input_location.derived_file_from(template: preprocessed_location_template).matching_locations_in_file_dir(tail_regexp: tail_regexp)
80
80
  end
81
81
 
82
82
  ##
83
83
  # @api public
84
84
  #
85
+ # @param splitter [#call]
86
+ #
85
87
  # Take the given PDF(s) and into one image per page. Remember that the URL should account for
86
88
  # the page number.
87
89
  #
@@ -98,22 +100,27 @@ module DerivativeRodeo
98
100
  # @see BaseGenerator#with_each_requisite_location_and_tmp_file_path for further discussion
99
101
  #
100
102
  # rubocop:disable Metrics/MethodLength
101
- def with_each_requisite_location_and_tmp_file_path
103
+ # rubocop:disable Metrics/AbcSize
104
+ def with_each_requisite_location_and_tmp_file_path(splitter: Services::PdfSplitter)
102
105
  input_files.each do |input_location|
103
106
  input_location.with_existing_tmp_path do |input_tmp_file_path|
104
107
  existing_locations = existing_page_locations(input_location: input_location)
105
108
 
106
109
  if existing_locations.count.positive?
107
- existing_locations.each do |location|
110
+ logger.info("#{self.class}##{__method__} found #{existing_locations.count} file(s) at existing split location for #{input_location.file_uri.inspect}.")
111
+ existing_locations.each_with_index do |location, index|
112
+ logger.info("#{self.class}##{__method__} found ##{index} split file #{location.file_path.inspect} for #{input_location.file_uri.inspect}.")
108
113
  yield(location, location.file_path)
109
114
  end
110
115
  else
116
+ logger.info("#{self.class}##{__method__} did not find at existing location split files for #{input_location.file_uri.inspect}. Proceeding with #{splitter}.call")
111
117
  # We're going to need to create the files and "cast" them to locations.
112
- Services::PdfSplitter.call(
118
+ splitter.call(
113
119
  input_tmp_file_path,
114
120
  image_extension: output_extension,
115
121
  image_file_basename_template: image_file_basename_template(basename: input_location.file_basename)
116
- ).each do |image_path|
122
+ ).each_with_index do |image_path, index|
123
+ logger.info("#{self.class}##{__method__} generated (via #{splitter}.call) ##{index} split file #{image_path.inspect} for #{input_location.file_uri.inspect}.")
117
124
  image_location = StorageLocations::FileLocation.new("file://#{image_path}")
118
125
  yield(image_location, image_path)
119
126
  end
@@ -121,7 +128,18 @@ module DerivativeRodeo
121
128
  end
122
129
  end
123
130
  end
131
+ # rubocop:enable Metrics/AbcSize
124
132
  # rubocop:enable Metrics/MethodLength
133
+
134
+ ##
135
+ # We're working with an input location with a filename basename of "123.ARCHIVAL--page-1.tiff"
136
+ # The :preprocessed_location_template, due to constraints, likely ends with the original PDF's
137
+ # filename (e.g. "123.ARCHIVAL.pdf")
138
+ #
139
+ # And since the template doesn't have a concept of page number, we introduce this kludge.
140
+ def derive_preprocessed_template_from(input_location:, preprocessed_location_template:)
141
+ File.join(File.dirname(preprocessed_location_template), input_location.file_name)
142
+ end
125
143
  end
126
144
  end
127
145
  end
@@ -33,6 +33,11 @@ module DerivativeRodeo
33
33
  File.open(path_to_coordinate, "w+") do |file|
34
34
  file.puts service.call(hocr_html).to_json
35
35
  end
36
+ rescue => e
37
+ message = "#{self.class}##{__method__} encountered `#{e.class}' error “#{e}” for path_to_hocr: #{path_to_hocr.inspect} and path_to_coordinate: #{path_to_coordinate.inspect}"
38
+ exception = RuntimeError.new(message)
39
+ exception.set_backtrace(e.backtrace)
40
+ raise exception
36
41
  end
37
42
  end
38
43
  end
@@ -46,11 +46,12 @@ module DerivativeRodeo
46
46
  # from_uris: ["file:///path1/A/file.pdf", "aws:///path2/B/file.pdf"],
47
47
  # template: "file:///dest1/{{dir_parts[-1..-1]}}/{{ filename }}")
48
48
  # => ["file:///dest1/A/file.pdf", "aws:///dest1/B/file.pdf"]
49
- def self.call(from_uri:, template:, adapter: nil, separator: "/")
50
- new(from_uri: from_uri, template: template, adapter: adapter, separator: separator).call
49
+ def self.call(from_uri:, template:, adapter: nil, separator: "/", **options)
50
+ new(from_uri: from_uri, template: template, adapter: adapter, separator: separator, **options).call
51
51
  end
52
52
 
53
- def initialize(from_uri:, template:, adapter: nil, separator: "/")
53
+ # rubocop:disable Metrics/MethodLength
54
+ def initialize(from_uri:, template:, adapter: nil, separator: "/", **options)
54
55
  @from_uri = from_uri
55
56
  @template = template
56
57
  @adapter = adapter
@@ -60,12 +61,23 @@ module DerivativeRodeo
60
61
  @from_scheme, @path = uri.split("://")
61
62
  @parts = @path.split(separator)
62
63
  @dir_parts = @parts[0..-2]
63
- @filename = @parts[-1]
64
- @basename = File.basename(@filename, ".*")
65
- @extension = File.extname(@filename)
64
+ @filename = options[:filename] || @parts[-1]
65
+ @basename = options[:basename] || File.basename(@filename, ".*")
66
+
67
+ ##
68
+ # HACK: Because the HocrGenerator has `.mono.tiff` and we are not interested in carrying
69
+ # forward the `.mono` suffix as that makes it hard to find the preprocessed word
70
+ # coordinates, alto, and plain text. This ensures files derived from the .mono are findable
71
+ # in IIIF Print.
72
+ @basename = @basename.sub(/\.mono\z/, '')
73
+ @extension = options[:extension] || File.extname(@filename)
74
+ # When a generator specifies "same" we want to use the given file's extension
75
+ @extension = File.extname(@filename) if @extension == DerivativeRodeo::StorageLocations::SAME
76
+ @extension = ".#{@extension}" unless @extension.start_with?(".")
66
77
 
67
78
  @template_without_query, @template_query = template.split("?")
68
79
  end
80
+ # rubocop:enable Metrics/MethodLength
69
81
 
70
82
  def call
71
83
  to_uri = template_without_query.gsub(DIR_PARTS_REPLACEMENT_REGEXP) do |text|
@@ -46,6 +46,8 @@ module DerivativeRodeo
46
46
  delegate :config, to: DerivativeRodeo
47
47
  end
48
48
 
49
+ delegate :logger, to: DerivativeRodeo
50
+
49
51
  ##
50
52
  # @param location_name [String]
51
53
  #
@@ -101,10 +103,10 @@ module DerivativeRodeo
101
103
  # @param service [#call, Module<DerivativeRodeo::Services::ConvertUriViaTemplateService>]
102
104
  #
103
105
  # @return [StorageLocations::BaseLocation]
104
- def self.build(from_uri:, template:, service: DerivativeRodeo::Services::ConvertUriViaTemplateService)
106
+ def self.build(from_uri:, template:, service: DerivativeRodeo::Services::ConvertUriViaTemplateService, **options)
105
107
  # HACK: Ensuring that we have the correct scheme. Maybe this is a hack?
106
108
  from_uri = "#{scheme}://#{from_uri}" unless from_uri.start_with?("#{scheme}://")
107
- to_uri = service.call(from_uri: from_uri, template: template, adapter: self)
109
+ to_uri = service.call(from_uri: from_uri, template: template, adapter: self, **options)
108
110
  new(to_uri)
109
111
  end
110
112
 
@@ -203,9 +205,9 @@ module DerivativeRodeo
203
205
  # @return [StorageLocations::BaseLocation]
204
206
  #
205
207
  # @see DerivativeRodeo::Services::ConvertUriViaTemplateService
206
- def derived_file_from(template:)
208
+ def derived_file_from(template:, **options)
207
209
  klass = DerivativeRodeo::StorageLocations::BaseLocation.load_location(template)
208
- klass.build(from_uri: file_path, template: template)
210
+ klass.build(from_uri: file_path, template: template, **options)
209
211
  end
210
212
 
211
213
  ##
@@ -231,6 +233,7 @@ module DerivativeRodeo
231
233
  def with_new_extension(extension)
232
234
  return file_path if extension == StorageLocations::SAME
233
235
 
236
+ # NOTE: May need to revisit this
234
237
  "#{file_path.split('.')[0]}.#{extension}"
235
238
  end
236
239
 
@@ -42,6 +42,9 @@ module DerivativeRodeo
42
42
  #
43
43
  # @param tail_regexp [Regexp]
44
44
  def matching_locations_in_file_dir(tail_regexp:)
45
+ logger.debug("#{self.class}##{__method__} searching for matching files in " \
46
+ "file_dir: #{file_dir.inspect} " \
47
+ "with tail_regexp: #{tail_regexp.inspect}.")
45
48
  Dir.glob(File.join(file_dir, "*")).each_with_object([]) do |filename, accumulator|
46
49
  accumulator << derived_file_from(template: "file://#{filename}") if tail_regexp.match(filename)
47
50
  end
@@ -73,7 +73,10 @@ module DerivativeRodeo
73
73
  def matching_locations_in_file_dir(tail_regexp:)
74
74
  uri = URI.parse(file_uri)
75
75
  scheme_and_host = "#{uri.scheme}://#{uri.host}"
76
-
76
+ logger.debug("#{self.class}##{__method__} searching for matching files for " \
77
+ "scheme_and_host: #{scheme_and_host.inspect} " \
78
+ "file_dir: #{file_dir.inspect} " \
79
+ "with tail_regexp: #{tail_regexp.inspect}.")
77
80
  bucket.objects(prefix: file_dir).each_with_object([]) do |object, accumulator|
78
81
  if tail_regexp.match(object.key)
79
82
  template = File.join(scheme_and_host, object.key)
@@ -120,7 +123,7 @@ module DerivativeRodeo
120
123
  def bucket_name
121
124
  @bucket_name ||= file_uri.match(%r{s3://(.+)\.s3})&.[](1)
122
125
  rescue StandardError
123
- raise Errors::BucketMissingError
126
+ raise Errors::BucketMissingError.new(file_uri: file_uri)
124
127
  end
125
128
 
126
129
  # @see .use_actual_s3_bucket
@@ -13,6 +13,8 @@ module DerivativeRodeo
13
13
  # Location to download and upload files to Sqs
14
14
  # It uploads a file_uri to the queue, not the contents of that file
15
15
  # reading from the queue is not currently implemented
16
+ #
17
+ # rubocop:disable Metrics/ClassLength
16
18
  class SqsLocation < BaseLocation
17
19
  ##
18
20
  # @!group Class Attributes
@@ -85,11 +87,14 @@ module DerivativeRodeo
85
87
  batch = []
86
88
  Dir.glob("#{File.dirname(tmp_file_path)}/**/**").each.with_index do |fp, i|
87
89
  batch << { id: SecureRandom.uuid, message_body: output_json("file://#{fp}") }
88
- if (i % batch_size).zero?
90
+ if (i + 1 % batch_size).zero?
89
91
  add_batch(messages: batch)
90
92
  batch = []
91
93
  end
92
94
  end
95
+
96
+ # Ensure we're flushing the batched up queue as part of completing the write.
97
+ add_batch(messages: batch) if batch.present?
93
98
  file_uri
94
99
  end
95
100
 
@@ -181,6 +186,7 @@ module DerivativeRodeo
181
186
  end
182
187
 
183
188
  def output_json(uri)
189
+ # TODO: Add ability to handle a pre-process-template given to an SQS, and pass that along to the generator when applicable.
184
190
  key = DerivativeRodeo::Services::ConvertUriViaTemplateService.call(from_uri: uri, template: template, adapter: self)
185
191
  { key => [template] }.to_json
186
192
  end
@@ -201,5 +207,6 @@ module DerivativeRodeo
201
207
  @file_uri_parts
202
208
  end
203
209
  end
210
+ # rubocop:enable Metrics/ClassLength
204
211
  end
205
212
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DerivativeRodeo
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: derivative-rodeo
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
  - Rob Kaufman
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-06-06 00:00:00.000000000 Z
12
+ date: 2023-07-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -337,7 +337,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
337
337
  - !ruby/object:Gem::Version
338
338
  version: '0'
339
339
  requirements: []
340
- rubygems_version: 3.1.6
340
+ rubygems_version: 3.3.7
341
341
  signing_key:
342
342
  specification_version: 4
343
343
  summary: An ETL Ecosystem for Derivative Processing.