file_pipeline 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ require_relative 'file_pipeline/errors'
6
+ require_relative 'file_pipeline/file_operations'
7
+ require_relative 'file_pipeline/versioned_file'
8
+ require_relative 'file_pipeline/pipeline'
9
+
10
+ # Module that contains classes to build a file processing pipeline that applies
11
+ # a defined batch of file operations non-destructively to a VersionedFile.
12
+ module FilePipeline
13
+ # Constant for the defualt directory for file operations.
14
+ DEFAULT_DIR = 'file_pipeline/file_operations/default_operations'
15
+
16
+ # Adds _directory_ to the Array of #source_directories which will be
17
+ # searched for source files when loading file operations.
18
+ # _directory_ will be prepended. Therefore, directories will be searcherd
19
+ # in reverse order of them being added.
20
+ def self.<<(directory)
21
+ directory_path = File.expand_path directory
22
+ return source_directories if source_directories.include? directory_path
23
+
24
+ no_dir = !File.directory?(directory_path)
25
+ raise Errors::SourceDirectoryError, dir: directory if no_dir
26
+
27
+ @src_directories.prepend directory_path
28
+ end
29
+
30
+ # Returns the constant for the <em>file_operation</em> class. If the
31
+ # constant is not defined, will try to require the source file.
32
+ def self.load(file_operation)
33
+ const = file_operation.split('_').map(&:capitalize).join
34
+ FilePipeline.load_file(file_operation) unless const_defined? const
35
+ const_get 'FileOperations::' + const
36
+ rescue NameError
37
+ # TODO: implement autogenerating module names from file_operation src path
38
+ const_get const
39
+ end
40
+
41
+ # Will search for <em>src_file</em> in .source_directories and require the
42
+ # file if.
43
+ def self.load_file(src_file)
44
+ src_file += '.rb' unless src_file.end_with? '.rb'
45
+ src_path = FilePipeline.source_path src_file
46
+ if src_path.nil?
47
+ raise Errors::SourceFileError,
48
+ file: src_file,
49
+ directories: FilePipeline.source_directories
50
+ end
51
+ require src_path
52
+ end
53
+
54
+ # Creates a file basename consisting of either a timestamp or a UUID,
55
+ # depending on the _kind_ argument (+:timestamp+ or +:random+; default:
56
+ # +:timestamp+)
57
+ def self.new_basename(kind = :timestamp)
58
+ case kind
59
+ when :random
60
+ SecureRandom.uuid
61
+ when :timestamp
62
+ Time.now.strftime('%Y-%m-%dT%H:%M:%S.%N')
63
+ end
64
+ end
65
+
66
+ # Returns a String with the <em>/directory/filename</em>.
67
+ def self.path(dir, filename)
68
+ File.join dir, filename
69
+ end
70
+
71
+ # Returns an array of directory paths that may contain source files for
72
+ # file operation classes.
73
+ def self.source_directories
74
+ return @src_directories if @src_directories
75
+
76
+ @src_directories = [FilePipeline.path(__dir__, DEFAULT_DIR)]
77
+ end
78
+
79
+ # Searches .source_directories and for _file_, and returns the full path
80
+ # (directory and filename) for the first match or nil if the file is
81
+ # nowhere found. Since directories are added in reverse order (see .<<)
82
+ # this will give redefinitions of file operations in custom directories
83
+ # precedence over the default directory, thus allowing overriding of file
84
+ # operation definitions.
85
+ def self.source_path(file)
86
+ FilePipeline.source_directories.each do |dir|
87
+ full_path = FilePipeline.path(dir, file)
88
+ return full_path if File.exist? full_path
89
+ end
90
+ nil
91
+ end
92
+ end
metadata ADDED
@@ -0,0 +1,303 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: file_pipeline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Martin Stein
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: multi_exiftool
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.11.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.11.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-vips
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.14
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.14
41
+ description: "= file_pipeline\n\nThe <tt>file_pipeline</tt> gem provides a framework
42
+ for nondestructive \napplication of file operation batches to files.\n\n== Installation\n\n
43
+ \ gem install file_pipeline\n\n== Dependencies\n\nThe file operations included in
44
+ the gem require \n{ruby-vips}[https://github.com/libvips/ruby-vips] for image manipulation
45
+ and\n{multi_exiftool}[https://github.com/janfri/multi_exiftool] for image file\nmetdata
46
+ extraction and manipulation.\n\nWhile these dependencies should be installed automatically
47
+ with the gem, \nruby-vips depends on {libvips}[https://libvips.github.io/libvips/],
48
+ and\nmulti_exiftool depends on\n{Exiftool}[https://www.sno.phy.queensu.ca/~phil/exiftool/],
49
+ which will not be\ninstalled automatically.\n\n== Usage\n\nThe basic usage is to
50
+ create a new FilePipeline::Pipeline object and define any\nfile operations that
51
+ are to be performed, apply it to a \nFilePipeline::VersionedFile object initialized
52
+ with the image to be processed,\nthen finalize the versioned file.\n\n require
53
+ 'file_pipeline'\n\n # create a new instance of Pipeline\n my_pipeline = FilePipeline::Pipeline.new\n\n
54
+ \ # configure an operation to scale an image to 1280 x 960 pixels\n my_pipeline.define_operation('scale',
55
+ :width => 1280, :height => 960)\n\n # create an instance of VersionedFile for the
56
+ file '~/image.jpg'\n image = FilePipeline::VersionedFile.new('~/image.jpg')\n\n
57
+ \ # apply the pipeline to the versioned file\n my_pipeline.apply_to(image)\n\n
58
+ \ # finalize the versioned file, replacing the original\n image.finalize(:overwrite
59
+ => true)\n\n=== Setting up a Pipeline\n\nPipeline objects can be set up to contain
60
+ default file operations included in\nthe gem or with custom file operations (see\n{Custom
61
+ file operations}[rdoc-label:label-Custom+file+operations] for\ninstructions on how
62
+ to create custom operations).\n\n==== Basic set up with default operations\n\nTo
63
+ define an operation, pass the class name of the operation in underscore\nnotation
64
+ and without the containing module name, and any options to\n{#define_operation}[rdoc-ref:FilePipeline::Pipeline#define_operation].\n\nThe
65
+ example below adds an instance of \nPtiffConversion[rdoc-ref:FilePipeline::FileOperations::PtiffConversion]
66
+ with\nthe <tt>:tile_width</tt> and <tt>:tile_height</tt> options each set to 64\npixels.\n\n
67
+ \ my_pipeline = FilePipeline::Pipeline.new\n my_pipeline.define_operation('ptiff_conversion',\n
68
+ \ :tile_width => 64, :tile_height => 64)\n\nChaining
69
+ is possible\n\n my_pipeline = FilePipeline::Pipeline.new\n my_pipeline.define_operation('scale',
70
+ :width => 1280, :height => 1024)\n .define_operation('exif_restoration')\n\nAlternatively,
71
+ operations can be defined during initialization by passing a\nblock to {#new}[rdoc-ref:FilePipeline::Pipeline.new].\n\n
72
+ \ my_pipeline = FilePipeline::Pipeline.new do |pipeline|\n pipeline.define_operation('scale',
73
+ :width => 1280, :height => 1024)\n pipeline.define_operation('exif_restoration')\n
74
+ \ end\n\nWhen using the default operations included in the gem, it is sufficient
75
+ to\ncall <tt>#define_operation</tt> with the desired operations and options.\n\n====
76
+ Using custom file operations\n\nWhen file operations are to be used that are not
77
+ included in the gem, place\nthe source files for the class definitions in one or
78
+ more directories and\ninitialize the Pipeline object with the directory paths. The
79
+ directories will\nbe added to the {source directories}[rdoc-ref:FilePipeline.source_directories].\n\nDirectories
80
+ are added to the source directories in reverse order, so that\ndirectories added
81
+ later will have precedence when searching source files. The\ndefault operations
82
+ directory included in the gem will be searched last. This\nallows overriding of
83
+ operations without changing the code in existing classes.\n\nIf, for example, there
84
+ are two directories with custom file operation classes,\n<tt>'~/custom_operations'</tt>
85
+ and <tt>'~/other_operations'</tt>, the new\ninstance of Pipeline can be set up to
86
+ look for source files first in\n<tt>'~/other_operations'</tt>, then in <tt>'~/custom_operations'</tt>,
87
+ and\nfinally in the included default operations.\n\nThe basename for source files
88
+ _must_ be the class name in underscore notation\nwithout the containing module name.
89
+ If, for example, the operation is\n<tt>FileOperations::MyOperation</tt>, the source
90
+ file basename should be\n<tt>'my_operation.rb'</tt>\n\n my_pipeline = FilePipeline::Pipeline.new('~/custom_operations',\n
91
+ \ '~/other_operations')\n my_pipeline.define_operation('my_operation')\n\nSee
92
+ {Custom file operations}[rdoc-label:label-Custom+file+operations] for \ninstructions
93
+ for how to write file operations.\n\n=== Nondestructive application to files\n\nPipelines[rdoc-ref:FilePipeline::Pipeline]
94
+ work on \n{versioned files}[rdoc-ref:FilePipeline::VersionedFile], which allow for\nnon-destructive
95
+ application of all file operations.\n\nTo create a versioned file, initialize an
96
+ instance with the path to the \noriginal file:\n\n # create an instance of VersionedFile
97
+ for the file '~/image.jpg'\n image = FilePipeline::VersionedFile.new('~/image.jpg')\n\nAs
98
+ long as no operations have been applied, this will have no effect in the\nfile system.
99
+ Only when the first operation is applied will VersionedFile\ncreate a {working directory}[rdoc-ref:FilePipeline::VersionedFile#directory]
100
+ in\nthe same directory as the original file. The working directory will have the\nname
101
+ of the file basename without extension and the suffix <tt>'_versions'</tt>\nadded.\n\nPipelines
102
+ can be applied to a singe versioned file with the\nthe {#apply_to}[rdoc-ref:FilePipeline::Pipeline#apply_to]
103
+ method of the pipeline\ninstance, or to an array of versioned files with the\n{#batch_apply}[rdoc-ref:FilePipeline::Pipeline#batch_apply]
104
+ method of the \npipeline instance.\n\n=== Accessing file metadata and captured data.\n\n*Limitations*:
105
+ this currently only works for _Exif_ metadata of image files.\n\nVersionedFile provides
106
+ access to a files metadata via the\n{#metadata}[rdoc-ref:FilePipeline::VersionedFile#metadata]
107
+ method of the \nversioned file instance.\n\nBoth the metadata for the original file
108
+ and the current (latest) version can\nbe accessed:\n\n image = FilePipeline::VersionedFile.new('~/image.jpg')\n\n
109
+ \ # access the metadata for the current version\n image.metadata\n\nNote that if
110
+ no file operations have been applied by a pipeline object, this\nwill return the
111
+ metadata for the original, which in that case is the current\n(latest) version.\n\nTo
112
+ explicitly get the metadata for the original file even if there are newer\nversions
113
+ available, pass the <tt>:for_version</tt> option with the symbol\n<tt>:original</tt>:\n\n
114
+ \ # access the metadata for the original file\n image.metadata(:for_version =>
115
+ :original)\n\nSome file operations can comprise metadata; many image processing
116
+ libraries\nwill not preserve all _Exif_ tags and their values when converting images
117
+ to\na different format, but only write a small subset of tags to the file they\ncreate.
118
+ In these cases, the \n{ExifRestoration}[rdoc-ref:FilePipeline::FileOperations::ExifRestoration]\noperation
119
+ can be used to try to restore the tags that have been discarded, but\nit can not
120
+ write all tags. It will store all tags and their values that it could \nnot write
121
+ back to the file and return them as captured data.\n\nLikewise, if the \n{ExifRedaction}[rdoc-ref:FilePipeline::FileOperations::ExifRedaction]
122
+ is applied\nto delete sensitive tags (e.g. GPS location data), it will return all
123
+ deleted \nexif tags and their values as captured data.\n\nThe\n{#recovered_metadata}[rdoc-ref:FilePipeline::VersionedFile#recovered_metadata]\nof
124
+ the versioned file instance will return a hash with all metadata that was\ndeleted
125
+ or could not be restored by operations:\n\n delete_tags = ['CreatorTool', 'Software']\n\n
126
+ \ my_pipeline = FilePipeline::Pipeline.new do |pipeline|\n pipeline.define_operation('scale',
127
+ width: 1280, height: 1024)\n pipeline.define_operation('exif_restoration')\n
128
+ \ Pipeline.define_operation('exif_redaction', :redact_tags => delete_tags)\n end\n\n
129
+ \ image = FilePipeline::VersionedFile.new('~/image.jpg')\n my_pipeline.apply_to(image)\n\n
130
+ \ image.recovered_metadata\n\nFor information on retrieving other kinds of captured
131
+ data, refer to\nthe versioned file instance methods\n{#captured_data}[rdoc-ref:FilePipeline::VersionedFile#captured_data],\n{#captured_data_for}[rdoc-ref:FilePipeline::VersionedFile#captured_data_for],
132
+ \nand\n{#captured_data_with}[rdoc-ref:FilePipeline::VersionedFile#captured_data_with].\n\n===
133
+ Finalizing files\n\nOnce all file operations of a pipeline object have been applied
134
+ to a\nversioned file object, it can be finalized by calling the\n{#finalize}[rdoc-ref:FilePipeline::VersionedFile#finalize]
135
+ method of the\ninstance.\n\nFinalization will write the current version to the same
136
+ directory that\ncontains the original. It will by default preserve the original
137
+ by adding\na suffix to the basename of the final version. If the <tt>:overwrite</tt>\noption
138
+ for the method is passed with +true+, it will delete the original and\nwrite the
139
+ final version to the same basename as the original.\n\n image = FilePipeline::VersionedFile.new('~/image.jpg')\n\n
140
+ \ # finalize the versioned file, preserving the original\n image.finalize\n\n #
141
+ finalize the versioned file, replacing the original\n image.finalize(:overwrite
142
+ => true)\n\nThe work directory with all other versions will be deleted after the
143
+ final\nversion has been written.\n\n== Custom file operations\n\n=== Module nesting\n\nFile
144
+ operation classes _must_ be defined in the FilePipeline::FileOperations\nmodule
145
+ for {automatic requiring}[rdoc-ref:FilePipeline.load] of source files to\nwork.\n\n===
146
+ Implementing from scratch\n\n==== Initializer\n\nThe <tt>#initialize</tt> method
147
+ _must_ take an +options+ argument (a hash\nwith a default value, or a <em>double
148
+ splat</em>) and _must_ be exposed\nthrough an <tt>#options</tt> getter method.\n\nThe
149
+ options passed can be any for the file operation to properly configure\na specific
150
+ instance of a method.\n\nThis requirement is imposed by the \n{#define_operation}[rdoc-ref:FilePipeline::Pipeline#define_operation]
151
+ instance\nmethod of Pipeline, which will automatically load and initialize an instance
152
+ of\nthe file operation with any options provided as a hash.\n\n===== Examples\n\n
153
+ \ class MyOperation\n attr_reader :options\n\n # initializer with a default\n
154
+ \ def initialize(options = {})\n @options = options\n end\n end\n\n class
155
+ MyOperation\n attr_reader :options\n\n # initializer with a double splat\n
156
+ \ def initialize(**options)\n @options = options\n end\n end\n\nConsider
157
+ a file operation +CopyrightNotice+ that whill add copyright\ninformation to an image
158
+ file's _Exif_ metadata, the value for the copyright\ntag could be passed as an option.\n\n
159
+ copyright_notice = CopyrightNotice.new(:copyright => 'The Photographer')\n\n====
160
+ The <tt>run</tt> method\n\nFile operations _must_ implement a <tt>#run</tt> method
161
+ that takes three\narguments (or a _splat_) in order to be used in a Pipeline.\n\n=====
162
+ Arguments\n\nThe three arguments required for implementations of <tt>#run</tt> are:\n*
163
+ the path to the <em>file to be modified</em>\n* the path to the _directory_ to which
164
+ new files will be saved.\n* the path to the <em>original file</em>, from which the
165
+ first version in a\n succession of modified versions has been created.\n\nThe <em>original
166
+ file</em> will only be used by file operations that require\nit for reference, e.g.
167
+ to restore file metadata that was compromised by\nother file operations.\n\n=====
168
+ Return value\n\nThe method _must_ return the path to the file that was created by
169
+ the\noperation (perferrably in the _directory_). It _may_ also return a \n{Results}[rdoc-ref:FilePipeline::FileOperations::Results]
170
+ object, containing the\noperation itself, a _success_ flag (+true+ or +false+),
171
+ and any logs or data\nreturned by the operation.\n\nIf results are returned with
172
+ the path to the created file, both values must\nbe wrapped in an array, with the
173
+ path as the first element, the results as\nthe second.\n\n===== Example\n\n def
174
+ run(src_file, directory, original)\n # make a path to which the created file
175
+ will be written\n out_file = File.join(directory, 'new_file_name.extension')\n\n
176
+ \ # create a Results object reporting success with no logs or data\n results
177
+ = Results.new(self, true, nil)\n\n # create a new out_file based on src_file
178
+ in directory\n # ...\n\n # return the path to the new file and the results
179
+ object\n [out_file, results]\n end\n\n==== Captured data tags\n\nCaptured data
180
+ tags can be used to\n{filter captured data}[rdoc-ref:FilePipeline::VersionedFile#captured_data_with]
181
+ \naccumulated during successive file operations.\n\nOperations that return data
182
+ as part of the results _should_ respond to\n<tt>:captured_data_tag</tt> and return
183
+ one of the \n{tag constants}[rdoc-ref:FilePipeline::FileOperations::CapturedDataTags].\n\n=====
184
+ Example\n\n # returns NO_DATA\n def captured_data_tag\n CapturedDataTags::NO_DATA\n
185
+ \ end\n\n=== Subclassing FileOperation\n\nThe {FileOperation}[rdoc-ref:FilePipeline::FileOperations::FileOperation]
186
+ class\nis an abstract superclass that provides a scaffold to facilitate the creation
187
+ of\nfile operations that conform to the requirements.\n\nIt implements a \n{#run}[rdoc-ref:FilePipeline::FileOperations::FileOperation#run]
188
+ method, that\ntakes the required three arguments and returns the path to the newly
189
+ created\nfile and a Results object.\n\nWhen the operation was successful,\n{success}[rdoc-ref:FilePipeline::FileOperations::Results#success]
190
+ will be\n+true+. When an exception was raised, that exeption will be rescued and
191
+ returned\nas the {log}[rdoc-ref:FilePipeline::FileOperations::Results#log], and\n{success}[rdoc-ref:FilePipeline::FileOperations::Results#success]
192
+ will be \n+false+.\n\nThe standard <tt>#run</tt> method of the FileOperation class
193
+ does not contain\nlogic to perform the actual file operation, but will call an \n{#operation
194
+ method}[rdoc-label:label-The+operation+method] that _must_ be\ndefined in the subclass
195
+ unless the subclass overrides the <tt>#run</tt> method.\n\nThe <tt>#run</tt> method
196
+ will generate the new path that is passed to the\n<tt>#operation</tt> method, and
197
+ to which the latter will write the new\nversion of the file. The new file path will
198
+ need an appropriate file type\nextension. The default behavior is to assume that
199
+ the extension will be the\nsame as for the file that was passed in as the basis
200
+ from which the new\nversion will be created. If the operation will result in a different
201
+ file\ntype, the subclass _should_ define a <tt>#target_extension</tt> method that\nreturns
202
+ the appropriate file extension (see\n{Target file extensions}[rdoc-label:label-Target+file+extensions]).\n\n====
203
+ Initializer\n\nThe +initialize+ method _must_ take an +options+ argument (a hash
204
+ with a\ndefault value or a <em>double splat</em>).\n\n===== Options and defaults\n\nThe
205
+ initializer can call +super+ and pass the +options+ hash and any\ndefaults (a hash
206
+ with default options). This will update the defaults with\nthe actual options passed
207
+ to +initialize+ and assign them to the\n{#options}[rdoc-ref:FilePipeline::FileOperations::FileOperation#options]
208
+ \nattribute.\n\nIf the initializer does not call +super+, it _must_ assign the options
209
+ to\nthe <tt>@options</tt> instance variable or expose them through an\n<tt>#options</tt>
210
+ getter method.\n\nIf it calls +super+ but must ensure some options are always set
211
+ to a\nspecific value, those should be set after the call to +super+.\n\n===== Examples\n\n
212
+ \ # initializer without defaults callings super\n def initialize(**options)\n super(options)\n
213
+ \ end\n\n # initializer with defaults calling super\n def initialize(**options)\n
214
+ \ defaults = { :option_a => true, :option_b => false }\n super(options, defaults)\n
215
+ \ end\n\n # initializer with defaults calling super, ensures :option_c => true\n
216
+ \ def initialize(**options)\n defaults = { :option_a => true, :option_b => false
217
+ }\n super(options, defaults)\n @options[:option_c] = true\n end\n\n # initilizer
218
+ that does not call super\n def initialize(**options)\n @options = options\n
219
+ \ end\n\n==== The <tt>operation</tt> method\n\nThe <tt>#operation</tt> method contains
220
+ the logic specific to a given\nsubclass of FileOperation and must be defined in
221
+ that subclass unless the\n<tt>#run</tt> method is overwritten.\n\n===== Arguments\n\nThe
222
+ <tt>#operation</tt> method must accept three arguments:\n\n* the path to the <em>file
223
+ to be modified</em>\n* the path for the <em>file to be created</em> by the operation.\n*
224
+ the path to the <em>original file</em>, from which the first version in a\n succession
225
+ of modified versions has been created.\n\nThe <em>original file</em> will only be
226
+ used by file operations that require\nit for reference, e.g. to restore file metadata
227
+ that was compromised by\nother file operations.\n\n===== Return Value\n\nThe method
228
+ _can_ return anything that can be interpreted by\n{LogDataParser}[rdoc-ref:FilePipeline::FileOperations::LogDataParser],\nincluding
229
+ nothing.\n\nIt will usually return any log outpout that the logic of <tt>#operation</tt>\nhas
230
+ generated, and/or data captured. If data is captured that is to be used\nlater,
231
+ the subclass should override the <tt>#captured_data_tag</tt> method to\nreturn the
232
+ appropriate \n{tag constant}[rdoc-ref:FilePipeline::FileOperations::CapturedDataTags].\n\n=====
233
+ Examples\n\n # creates out_file based on src_file, captures metadata differences\n
234
+ \ # between out_file and original, returns log messages and captured data\n def
235
+ operation(src_file, out_file, original)\n captured_data = {}\n log_messages
236
+ = []\n\n # write the new version based on src_file to out_file\n # compare
237
+ metadata of out_file with original, store any differences\n # in captures_data
238
+ and append any log messages to log_messages\n\n [log_messages, captured_data]\n
239
+ \ end\n\n # takes the third argument for the original file but does not use it\n
240
+ \ # creates out_file based on src_file, returns log messages\n def operation(src_file,
241
+ out_file, _)\n src_file, out_file = args\n log_messages = []\n\n # write
242
+ the new version based on src_file to out_file\n\n log_messages\n end\n\n #
243
+ takes arguments as a splat and destructures them to avoid having the\n # unused
244
+ thirs argumen\n # creates out_file based on src_file, returns nothing\n def operation(*args)\n
245
+ \ src_file, out_file = args\n\n # write the new version based on src_file to
246
+ out_file\n\n return\n end\n\n==== Target file extensions\n\nIf the file that
247
+ the operation creates is of a different type than the file\nthe version is based
248
+ upon, the class _must_ define the\n<tt>#target_extension</tt> method that returns
249
+ the appropriate file type\nextension.\n\nIn most cases, the resulting file type
250
+ will be predictable (static), and in\nsuch cases, the method can just return a string
251
+ with the extension.\n\nAn alternative would be to provide the expected extension
252
+ as an #option\nto the initializer.\n\n===== Examples\n\n # returns always '.tiff.\n
253
+ \ def target_extension\n '.tiff'\n end\n\n # returns the extension specified
254
+ in #options +:extension+\n # my_operation = MyOperation(:extension => '.dng')\n
255
+ \ def target_extension\n options[:extension]\n end\n"
256
+ email: loveablelobster@fastmail.fm
257
+ executables: []
258
+ extensions: []
259
+ extra_rdoc_files: []
260
+ files:
261
+ - LICENSE
262
+ - lib/file_pipeline.rb
263
+ - lib/file_pipeline/errors.rb
264
+ - lib/file_pipeline/errors/failed_modification_error.rb
265
+ - lib/file_pipeline/errors/missing_version_file_error.rb
266
+ - lib/file_pipeline/errors/source_directory_error.rb
267
+ - lib/file_pipeline/errors/source_file_error.rb
268
+ - lib/file_pipeline/file_operations.rb
269
+ - lib/file_pipeline/file_operations/captured_data_tags.rb
270
+ - lib/file_pipeline/file_operations/default_operations/exif_redaction.rb
271
+ - lib/file_pipeline/file_operations/default_operations/exif_restoration.rb
272
+ - lib/file_pipeline/file_operations/default_operations/ptiff_conversion.rb
273
+ - lib/file_pipeline/file_operations/default_operations/scale.rb
274
+ - lib/file_pipeline/file_operations/exif_manipulable.rb
275
+ - lib/file_pipeline/file_operations/file_operation.rb
276
+ - lib/file_pipeline/file_operations/log_data_parser.rb
277
+ - lib/file_pipeline/file_operations/results.rb
278
+ - lib/file_pipeline/pipeline.rb
279
+ - lib/file_pipeline/versioned_file.rb
280
+ homepage: https://github.com/loveablelobster/file_pipeline
281
+ licenses:
282
+ - MIT
283
+ metadata: {}
284
+ post_install_message:
285
+ rdoc_options: []
286
+ require_paths:
287
+ - lib
288
+ required_ruby_version: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - ">="
291
+ - !ruby/object:Gem::Version
292
+ version: '0'
293
+ required_rubygems_version: !ruby/object:Gem::Requirement
294
+ requirements:
295
+ - - ">="
296
+ - !ruby/object:Gem::Version
297
+ version: '0'
298
+ requirements: []
299
+ rubygems_version: 3.0.3
300
+ signing_key:
301
+ specification_version: 4
302
+ summary: Nondestructive file processing with a defined batch
303
+ test_files: []