file_pipeline 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51b495ebce21752d34f27eef33a350539f8918076774063323e603cde82a7726
4
- data.tar.gz: f6bd55e7129fe2308193af2e12630a867cca866ae6ce71dd177a9deea3e2aa00
3
+ metadata.gz: 3b25bf4205819b6d0df2fcbb9a879c7855fc2b0a3cb973774049f44a7d6965b8
4
+ data.tar.gz: 1191020063c4bb7669e9dfd88a03d539c7b081cd2f422decd87ac6bca288c86e
5
5
  SHA512:
6
- metadata.gz: 47077cb546d41be57b9c662718710c815dda1c3d77b1834946800d2ddf5472abfde8ecf9e107f58b69f6eb573ea536464a316c5d60a5ae97e94a4d5deb6fbc01
7
- data.tar.gz: 25b006ee9e71a989eea5f22eece86918feabe4f7579cf96425110cf64efe5f0e00ab40335b6002298d552f979894bbcd210869ce8e724c35628306d8da701e1e
6
+ metadata.gz: 8ccb8dabb09322d7c3b6b258c8e4e06edb3a7774e36c1fc26421794b8c53915b219a85c8c9bf07da2bf203cd949a8fc484a1db1cb8ddf6d8443a5c2ba724d4ff
7
+ data.tar.gz: 3e5a2ec367ab3a2d7280c9d6daaa41f6e3cadcba9b1b73bfdb891d4fef781bade2e4e5ffe970aee76e175ce43ea3ae82d967367f18b29f59dbffec180825262e
data/README.rdoc CHANGED
@@ -54,7 +54,7 @@ instructions on how to create custom operations).
54
54
  ==== Basic set up with default operations
55
55
 
56
56
  To define an operation, pass the class name of the operation in underscore
57
- notation and without the containing module name, and any options to
57
+ notation without the containing module name, and any options to
58
58
  {#define_operation}[rdoc-ref:FilePipeline::Pipeline#define_operation].
59
59
 
60
60
  The example below adds an instance of
@@ -87,8 +87,9 @@ call <tt>#define_operation</tt> with the desired operations and options.
87
87
 
88
88
  When file operations are to be used that are not included in the gem, place
89
89
  the source files for the class definitions in one or more directories and
90
- initialize the Pipeline object with the directory paths. The directories will
91
- be added to the {source directories}[rdoc-ref:FilePipeline.source_directories].
90
+ initialize the Pipeline object with the paths to those directories. The
91
+ directories will be added to the
92
+ {source directories}[rdoc-ref:FilePipeline.source_directories].
92
93
 
93
94
  Directories are added to the source directories in reverse order, so that
94
95
  directories added later will have precedence when searching source files. The
@@ -103,7 +104,7 @@ finally in the included default operations.
103
104
 
104
105
  The basename for source files _must_ be the class name in underscore notation
105
106
  without the containing module name. If, for example, the operation is
106
- <tt>FileOperations::MyOperation</tt>, the source file basename should be
107
+ <tt>FileOperations::MyOperation</tt>, the source file basename has to be
107
108
  <tt>'my_operation.rb'</tt>
108
109
 
109
110
  my_pipeline = FilePipeline::Pipeline.new('~/custom_operations',
@@ -146,7 +147,7 @@ VersionedFile provides access to a files metadata via the
146
147
  {#metadata}[rdoc-ref:FilePipeline::VersionedFile#metadata] method of the
147
148
  versioned file instance.
148
149
 
149
- Both the metadata for the original file and the current (latest) version can
150
+ Metadata for the original file, the current (latest) or an arbitrary version can
150
151
  be accessed:
151
152
 
152
153
  image = FilePipeline::VersionedFile.new('~/image.jpg')
@@ -167,12 +168,13 @@ versions available, pass the <tt>:for_version</tt> option with the symbol
167
168
 
168
169
  Some file operations can comprise metadata; many image processing libraries
169
170
  will not preserve all _Exif_ tags and their values when converting images to
170
- a different format, but only write a small subset of tags to the file they
171
- create. In these cases, the
171
+ a different format, but only write a subset of tags to the file they create.
172
+ In these cases, the
172
173
  {ExifRestoration}[rdoc-ref:FilePipeline::FileOperations::ExifRestoration]
173
- operation can be used to try to restore the tags that have been discarded, but
174
- it can not write all tags. It will store all tags and their values that it could
175
- not write back to the file and return them as captured data.
174
+ operation can be used to try to restore the tags that have been discarded. The
175
+ operation uses Exiftool to write tags, and Exiftool will not write all tags.
176
+ It will store any tags and their values that it could not write back to the file
177
+ and return them as captured data.
176
178
 
177
179
  Likewise, if the
178
180
  {ExifRedaction}[rdoc-ref:FilePipeline::FileOperations::ExifRedaction] is applied
@@ -8,32 +8,64 @@ module FilePipeline
8
8
  # The file opration that caused the error.
9
9
  attr_reader :info
10
10
 
11
- # FIXME: should contain original file name file name!
11
+ # Returns a new instance.
12
+ #
13
+ # ===== Arguments
14
+ #
15
+ # * +msg+ - error message for the exception. If none provided, the
16
+ # instance will be initialized with the #default_message.
17
+ #
18
+ # ===== Options
19
+ #
20
+ # * <tt>info</tt> - a FileOperations::Results object or an object.
21
+ # * <tt>file</tt> - path to the file thas was being processed.
12
22
  def initialize(msg = nil, info: nil, file: nil)
13
23
  @file = file
14
24
  @info = info
15
-
16
- if info.respond_to?(:operation) && info.respond_to?(:log)
17
- msg ||= "#{@info.operation&.name} with options"\
18
- " #{@info.operation&.options} failed on #{file}."
19
- if original_error
20
- msg += "\nException raised by the operation:"\
21
- " #{original_error.inspect}. Backtrace:\n"
22
- msg += original_backtrace if original_backtrace
23
- end
24
- else
25
- msg ||= 'Operation failed' unless info
26
- end
25
+ msg ||= default_message
27
26
  super msg
28
27
  end
29
28
 
29
+ # Returns the backtrace of the error that caused the exception.
30
30
  def original_backtrace
31
31
  original_error&.backtrace&.join("\n")
32
32
  end
33
33
 
34
+ # Returns the error that caused the exception.
34
35
  def original_error
35
36
  @info.log.find { |item| item.is_a? Exception }
36
37
  end
38
+
39
+ private
40
+
41
+ # Appends the backtrace of the error that caused the exception to the
42
+ # #default_message.
43
+ def append_backtrace(str)
44
+ return str + "\n" unless original_backtrace
45
+
46
+ str + " Backtrace:\n#{original_backtrace}"
47
+ end
48
+
49
+ # Appends the message of the error that caused the exception to the
50
+ # #default_message.
51
+ def append_error(str)
52
+ return str unless original_error
53
+
54
+ str += "\nException raised by the operation:"\
55
+ " #{original_error.inspect}."
56
+ append_backtrace str
57
+ end
58
+
59
+ # Returns a String with the #message for +self+.
60
+ def default_message
61
+ if info&.respond_to?(:operation) && info&.respond_to?(:log)
62
+ msg = "#{info.operation&.name} with options"\
63
+ " #{info.operation&.options} failed on #{@file}."
64
+ append_error msg
65
+ else
66
+ 'Operation failed'
67
+ end
68
+ end
37
69
  end
38
70
  end
39
71
  end
@@ -19,6 +19,35 @@ module FilePipeline
19
19
  # by #finalize is not replacing the original.
20
20
  attr_reader :target_suffix
21
21
 
22
+ extend Forwardable
23
+
24
+ # Returns a two-dimesnional array, where each nested array has two items;
25
+ # the file operation object and data captured by the operartion (if any).
26
+ #
27
+ # <tt>[[description_object, data_or_nil], ...]</tt>
28
+ delegate captured_data: :history
29
+
30
+ # Returns any data captured by <tt>operation_name</tt>.
31
+ #
32
+ # If multiple instances of one operation class have modified the file,
33
+ # pass any +options+ the specific instance of the operation was initialized
34
+ # with as the optional second argument.
35
+ delegate captured_data_for: :history
36
+
37
+ # Returns an array with all data captured by operations with +tag+.
38
+ #
39
+ # Tags are defined in FileOperations::CapturedDataTags
40
+ delegate captured_data_with: :history
41
+
42
+ # Returns an array of triplets (arryas with three items each): the name of
43
+ # the file operation class (a string), options (a hash), and the actual log
44
+ # (an array).
45
+ delegate log: :history
46
+
47
+ # Returns an array with paths to the version files of +self+ (excluding
48
+ # #original).
49
+ delegate versions: :history
50
+
22
51
  # Returns a new instance with +file+ as the #original.
23
52
  #
24
53
  # ===== Arguments
@@ -41,23 +70,10 @@ module FilePipeline
41
70
 
42
71
  @original = file
43
72
  @basename = File.basename(file, '.*')
44
- @history = {}
73
+ @history = Versions::History.new
45
74
  @directory = nil
46
75
  @target_suffix = target_suffix
47
- end
48
-
49
- # Copies the file with path _src_ to <em>/dir/filename</em>.
50
- def self.copy(src, dir, filename)
51
- dest = FilePipeline.path(dir, filename)
52
- FileUtils.cp src, dest
53
- dest
54
- end
55
-
56
- # Moves the file with path _src_ to <em>/dir/filename</em>.
57
- def self.move(src, dir, filename)
58
- dest = FilePipeline.path(dir, filename)
59
- FileUtils.mv src, dest
60
- dest
76
+ history[original] = nil
61
77
  end
62
78
 
63
79
  # Adds a new version to #history and returns _self_.
@@ -80,37 +96,6 @@ module FilePipeline
80
96
  raise e
81
97
  end
82
98
 
83
- # Returns a two-dimesnional array, where each nested array has two items;
84
- # the file operation object and data captured by the operartion (if any).
85
- #
86
- # <tt>[[description_object, data_or_nil], ...]</tt>
87
- def captured_data
88
- filter_history :data
89
- end
90
-
91
- # Returns any data captured by <tt>operation_name</tt>.
92
- #
93
- # If multiple instances of one operation class have modified the file,
94
- # pass any +options+ the specific instance of the operation was initialized
95
- # with as the optional second argument.
96
- def captured_data_for(operation_name, **options)
97
- raw_data = captured_data.filter do |operation, _|
98
- operation.name == operation_name &&
99
- options.all? { |k, v| operation.options[k] == v }
100
- end
101
- raw_data.map(&:last)
102
- end
103
-
104
- # Returns an array with all data captured by operations with +tag+ has.
105
- #
106
- # Tags are defined in FileOperations::CapturedDataTags
107
- def captured_data_with(tag)
108
- return unless changed?
109
-
110
- captured_data.select { |operation, _| operation.captured_data_tag == tag }
111
- .map(&:last)
112
- end
113
-
114
99
  # Returns +true+ if there are #versions (file has been modified).
115
100
  #
116
101
  # *Warning:* It will also return +true+ if the file has been cloned.
@@ -122,7 +107,7 @@ module FilePipeline
122
107
  # the file to history, but no FileOperations::Results.
123
108
  def clone
124
109
  filename = FilePipeline.new_basename + current_extension
125
- clone_file = VersionedFile.copy(current, directory, filename)
110
+ clone_file = Versions.copy(current, directory, filename)
126
111
  self << clone_file
127
112
  end
128
113
 
@@ -161,19 +146,11 @@ module FilePipeline
161
146
  yield(self) if block_given?
162
147
  filename = overwrite ? replacing_trarget : preserving_taget
163
148
  FileUtils.rm original if overwrite
164
- @original = VersionedFile.copy(current, original_dir, filename)
149
+ @original = Versions.copy(current, original_dir, filename)
165
150
  ensure
166
151
  reset
167
152
  end
168
153
 
169
- # Returns an array of triplets (arryas with three items each): the name of
170
- # the file operation class (a string), options (a hash), and the actual log
171
- # (an array).
172
- def log
173
- filter_history(:log)
174
- .map { |operation, info| [operation.name, operation.options, info] }
175
- end
176
-
177
154
  # Returns the Exif metadata
178
155
  #
179
156
  # ===== Options
@@ -186,10 +163,12 @@ module FilePipeline
186
163
  #--
187
164
  # TODO: when file is not an image file, this should return other metadata
188
165
  # than exif.
189
- # TODO: implement the option to return metadata for a specif version index
190
166
  #++
191
167
  def metadata(for_version: :current)
192
- file = public_send for_version
168
+ if %i[current original].include? for_version
169
+ file = public_send(for_version)
170
+ end
171
+ file ||= for_version
193
172
  read_exif(file).first
194
173
  end
195
174
 
@@ -218,29 +197,15 @@ module FilePipeline
218
197
  # Returns a hash into which all captured data from file operations with the
219
198
  # FileOperations::CapturedDataTags::DROPPED_EXIF_DATA has been merged.
220
199
  def recovered_metadata
200
+ return unless changed?
221
201
  captured_data_with(FileOperations::CapturedDataTags::DROPPED_EXIF_DATA)
222
202
  &.reduce({}) { |recovered, data| recovered.merge data }
223
203
  end
224
204
 
225
- # Returns an array with paths to the version files of +self+ (excluding
226
- # #original).
227
- def versions
228
- history.keys
229
- end
230
-
231
205
  alias touch clone
232
206
 
233
207
  private
234
208
 
235
- # item = :data or :log
236
- def filter_history(item)
237
- history.inject([]) do |results, (_, info)|
238
- next results unless info.respond_to?(item) && info.public_send(item)
239
-
240
- results << [info.operation, info.public_send(item)]
241
- end
242
- end
243
-
244
209
  # Returns the filename for a target file that will not overwrite the
245
210
  # original.
246
211
  def preserving_taget
@@ -256,16 +221,18 @@ module FilePipeline
256
221
  # Deletes the work directory and resets #versions
257
222
  def reset
258
223
  FileUtils.rm_r directory, force: true
259
- @history = {}
224
+ history.clear!
260
225
  end
261
226
 
262
227
  # Validates if file exists and has been stored in #directory.
263
228
  def validate(file)
229
+ return current unless file
230
+
264
231
  raise Errors::MissingVersionFileError, file: file unless File.exist? file
265
232
 
266
233
  return file if File.dirname(file) == directory
267
234
 
268
- VersionedFile.move file, directory, File.basename(file)
235
+ Versions.move file, directory, File.basename(file)
269
236
  end
270
237
 
271
238
  # Creates the directory containing all version files. Directory name is
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FilePipeline
4
+ module Versions
5
+ # History objects keep track of a VersionedFile instances versions names and
6
+ # any associated logs or data for each version.
7
+ class History
8
+ # Returns a new instance.
9
+ def initialize
10
+ @entries = {}
11
+ end
12
+
13
+ # Retrieves the _results_ object for the <tt>version_name</tt>.
14
+ def [](version_name)
15
+ @entries[version_name]
16
+ end
17
+
18
+ # Associates the +results+ with the <tt>version_name</tt>.
19
+ def []=(version_name, results)
20
+ entry = @entries.fetch version_name, []
21
+ entry << results
22
+ @entries[version_name] = entry.compact
23
+ end
24
+
25
+ # Returns a two-dimensional array, where each nested array has two items:
26
+ # * the file operation object
27
+ # * data captured by the operartion (if any).
28
+ #
29
+ # <tt>[[file_operation_object, data_or_nil], ...]</tt>
30
+ def captured_data
31
+ filter :data
32
+ end
33
+
34
+ # Returns any data captured by <tt>operation_name</tt>.
35
+ #
36
+ # If multiple instances of one operation class have modified the file,
37
+ # pass any +options+ the specific instance of the operation was
38
+ # initialized with as the optional second argument.
39
+ def captured_data_for(operation_name, **options)
40
+ return if empty?
41
+
42
+ captured_data.filter { |op, _| matches? op, operation_name, options }
43
+ .map(&:last)
44
+ end
45
+
46
+ # Returns an array with all data captured by operations with +tag+.
47
+ # Returns an empty array if there is no data for +tag+.
48
+ #
49
+ # Tags are defined in FileOperations::CapturedDataTags
50
+ def captured_data_with(tag)
51
+ captured_data.filter { |op, _| op.captured_data_tag == tag }
52
+ .map(&:last)
53
+ end
54
+
55
+ # Clears all history entries (version names and associated results).
56
+ def clear!
57
+ @entries.clear
58
+ end
59
+
60
+ # Returns +true+ if +self+ has no entries (version names and associated
61
+ # results), +true+ otherwise.
62
+ def empty?
63
+ @entries.empty?
64
+ end
65
+
66
+ # Returns an array of triplets (arryas with three items each):
67
+ # * Name of the file operation class (String).
68
+ # * Options for the file operation instance (Hash).
69
+ # * The log (Array).
70
+ def log
71
+ filter(:log).map { |op, results| [op.name, op.options, results] }
72
+ end
73
+
74
+ # Returns a two-dimensional Array where every nested Array will consist
75
+ # of the version name (file path) at index +0+ and +nil+ or an Array with
76
+ # all _results_ objects for the version at index +1+:
77
+ #
78
+ # <tt>[version_name, [results1, ...]]</tt>
79
+ def to_a
80
+ @entries.to_a
81
+ end
82
+
83
+ # Returns an array with paths to the version files of +self+ (excluding
84
+ # #original).
85
+ def versions
86
+ @entries.keys
87
+ end
88
+
89
+ private
90
+
91
+ # Filters entries in self by +item+ (<tt>:log</tt> or <tt>:data</tt>).
92
+ def filter(item)
93
+ @entries.values.flatten.select(&item).map do |results|
94
+ [results.operation, results.public_send(item)]
95
+ end
96
+ end
97
+
98
+ # Returns +true+ if +name+ matches the _name_ attribute of +operation+ and
99
+ # +options+ matches the options the operation instance is initialized
100
+ # with.
101
+ def matches?(operation, name, opts)
102
+ operation.name == name && opts.all? { |k, v| operation.options[k] == v }
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'versions/history'
4
+
5
+ module FilePipeline
6
+ # Module that contains classes to work with VersionedFile.
7
+ module Versions
8
+ # Copies the file with path _src_ to <em>/dir/filename</em>.
9
+ def self.copy(src, dir, filename)
10
+ dest = FilePipeline.path(dir, filename)
11
+ FileUtils.cp src, dest
12
+ dest
13
+ end
14
+
15
+ # Moves the file with path _src_ to <em>/dir/filename</em>.
16
+ def self.move(src, dir, filename)
17
+ dest = FilePipeline.path(dir, filename)
18
+ FileUtils.mv src, dest
19
+ dest
20
+ end
21
+ end
22
+ end
data/lib/file_pipeline.rb CHANGED
@@ -4,6 +4,7 @@ require 'securerandom'
4
4
 
5
5
  require_relative 'file_pipeline/errors'
6
6
  require_relative 'file_pipeline/file_operations'
7
+ require_relative 'file_pipeline/versions'
7
8
  require_relative 'file_pipeline/versioned_file'
8
9
  require_relative 'file_pipeline/pipeline'
9
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file_pipeline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Stein
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-19 00:00:00.000000000 Z
11
+ date: 2019-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_exiftool
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.0.16
41
- description: The file_pipeline gem provides a framework fornondestructive application
41
+ description: The file_pipeline gem provides a framework for nondestructive application
42
42
  of file operation batches to files.
43
43
  email: loveablelobster@fastmail.fm
44
44
  executables: []
@@ -65,6 +65,8 @@ files:
65
65
  - lib/file_pipeline/file_operations/results.rb
66
66
  - lib/file_pipeline/pipeline.rb
67
67
  - lib/file_pipeline/versioned_file.rb
68
+ - lib/file_pipeline/versions.rb
69
+ - lib/file_pipeline/versions/history.rb
68
70
  homepage: https://github.com/loveablelobster/file_pipeline
69
71
  licenses:
70
72
  - MIT
@@ -77,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
79
  requirements:
78
80
  - - ">="
79
81
  - !ruby/object:Gem::Version
80
- version: '0'
82
+ version: '2.6'
81
83
  required_rubygems_version: !ruby/object:Gem::Requirement
82
84
  requirements:
83
85
  - - ">="