file_pipeline 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []