julik-depix 1.0.2 → 1.0.3

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.
data/History.txt CHANGED
@@ -1,4 +1,9 @@
1
- === 1.0.2 / 2008-12-23
1
+ === 1.0.4 / 2008-12-25
2
+
3
+ * Add brief inspect
4
+ * Add metadata editing
5
+
6
+ === 1.0.3 / 2008-12-23
2
7
 
3
8
  * Do not cleanup null bytes and terminators in the middle of char[] attributes (something useful might be in there)
4
9
 
data/Manifest.txt CHANGED
@@ -7,9 +7,13 @@ bin/depix-describe
7
7
  lib/depix.rb
8
8
  lib/depix/struct_explainer.rb
9
9
  lib/depix/structs.rb
10
+ lib/depix/benchmark.rb
10
11
  lib/depix/compact_structs.rb
11
12
  lib/depix/enums.rb
12
13
  lib/depix/dict.rb
14
+ lib/depix/reader.rb
15
+ lib/depix/editor.rb
16
+ test/test_dict.rb
13
17
  test/test_depix.rb
14
18
  test/samples/E012_P001_L000002_lin.0001.dpx
15
19
  test/samples/E012_P001_L000002_lin.0002.dpx
data/README.txt CHANGED
@@ -4,12 +4,22 @@
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Read DPX file metadata
7
+ Read and write DPX file metadata
8
8
 
9
9
  == SYNOPSIS:
10
10
 
11
+ Reading headers
12
+
11
13
  meta = Depix.from_file(dpx_file_path)
12
14
  puts meta.time_code #=> 10:00:00:02
15
+
16
+ Writing headers
17
+
18
+ editor = Depix::Editor.new(dpx_file_path)
19
+
20
+ # Advance the time code by one frame and save
21
+ editor.headers.time_code = editor.headers.time_code + 1
22
+ editor.commit!
13
23
 
14
24
  The data returned is described in the DPX_HEADER_STRUCTURE[link:files/DPX_HEADER_STRUCTURE_txt.html]. It's
15
25
  a vanilla Ruby object with no extra methods except for the readers that have the same name as the specified
@@ -19,11 +29,15 @@ The gem also contains an executable called depix-desribe which can be used from
19
29
 
20
30
  $book depix-describe 001_PTAPE_001.001.dpx
21
31
 
22
- == NOTES:
32
+ for a long description or
33
+
34
+ $book depix-describe -s 001_PTAPE_001.001.dpx
23
35
 
24
- In the future there will be a possibility to modify and commit the headers, but it's not a priority at this time.
36
+ for a short description
37
+
38
+ == NOTES:
25
39
 
26
- Autodesk IFFS systems write the reel name for the file to the orientation.device field
40
+ Autodesk IFFS systems write the reel name for the file to the orientation.device field, some scanners write it into user data.
27
41
 
28
42
  == REQUIREMENTS:
29
43
 
data/bin/depix-describe CHANGED
@@ -10,13 +10,24 @@ OptionParser.new do |opts|
10
10
  opts.on("-c", "--compact", "Compact output (only fields that change per frame)") do |v|
11
11
  options[:compact] = true
12
12
  end
13
+
14
+ opts.on("-s", "--synthetics", "Output only synthetic fields (like time code and aspect)") do |v|
15
+ options[:synthetics] = true
16
+ end
17
+
13
18
  end.parse!
14
19
 
15
20
  ARGV.each do | file |
16
- puts "Describing DPX #{file}. Empty elements are omitted.\n\n"
17
- puts "===================================================\n\n"
21
+ puts "Describing DPX #{file}. Empty elements are omitted."
22
+ puts "===================================================\n"
18
23
  begin
19
- puts Depix.describe_file(file, options[:compact])
24
+ if options[:synthetics]
25
+ puts Depix.describe_brief(file)
26
+ elsif options[:compact]
27
+ puts Depix.describe_file(file, true)
28
+ else
29
+ puts Depix.describe_file(file)
30
+ end
20
31
  rescue Depix::InvalidHeader
21
32
  puts " - Invalid header data"
22
33
  end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../depix'
2
+
3
+ require 'benchmark'
4
+
5
+ iter = 10000
6
+
7
+ puts "Reading DPX header #{iter} times, all data"
8
+ puts Benchmark.measure {
9
+ iter.times { Depix.from_file(File.dirname(__FILE__)+"/../../test/samples/026_FROM_HERO_TAPE_5-3-1_MOV.0029.dpx", false) }
10
+ }
11
+
12
+ puts "Reading DPX header #{iter} times, compact data"
13
+ puts Benchmark.measure {
14
+ iter.times { Depix.from_file(File.dirname(__FILE__)+"/../../test/samples/026_FROM_HERO_TAPE_5-3-1_MOV.0029.dpx", true) }
15
+ }
data/lib/depix/dict.rb CHANGED
@@ -50,6 +50,26 @@ module Depix
50
50
  def consume!(stack)
51
51
  clean(stack.shift)
52
52
  end
53
+
54
+ # Check that the passed value:
55
+ # a) Matches the Ruby type expected
56
+ # b) Fits into the slot
57
+ # c) Does not overflow
58
+ # When the validation fails should raise
59
+ def validate!(value)
60
+ raise "#{name} value required, but got nil in #{name}".strip if value.nil? && req?
61
+ raise "Value expected to be #{rtype} but was #{value.class}" if !value.nil? && rtype && !value.is_a?(rtype)
62
+ end
63
+
64
+ # Pack a value passed into a string
65
+ def pack(value)
66
+ raise "No pattern defined for #{self}" unless pattern
67
+ if value.nil?
68
+ [self.class.const_get(:BLANK)].pack(pattern)
69
+ else
70
+ [value].pack(pattern)
71
+ end
72
+ end
53
73
  end
54
74
 
55
75
  class U32Field < Field
@@ -67,6 +87,14 @@ module Depix
67
87
  def clean(value)
68
88
  value == BLANK ? nil : value
69
89
  end
90
+
91
+ # Override - might be Bignum although cast to Integer sometimes
92
+ def validate!(value)
93
+ raise "#{name} value required, but got nil".strip if value.nil? && req?
94
+ raise "#{name} value expected to be #{rtype} but was #{value.class}" if !value.nil? && (!value.is_a?(Integer) && !value.is_a?(Bignum))
95
+ raise "#{name} value #{value} overflows" if !value.nil? && (value < 0 || value >= BLANK)
96
+ end
97
+
70
98
  end
71
99
 
72
100
  class U8Field < Field
@@ -87,7 +115,12 @@ module Depix
87
115
  end
88
116
 
89
117
  def clean(v)
90
- v == BLANK ? nil : v
118
+ (v == BLANK || v == -1) ? nil : v
119
+ end
120
+
121
+ def validate!(value)
122
+ super(value)
123
+ raise "#{name} value #{value} out of bounds for 8 bit unsigned int".lstrip if (!value.nil? && (value < 0 || value >= BLANK))
91
124
  end
92
125
  end
93
126
 
@@ -101,6 +134,10 @@ module Depix
101
134
  def consume(stack)
102
135
  nil
103
136
  end
137
+
138
+ def pack(data)
139
+ raise "This is a filler, it cannot be reconstructed from a value"
140
+ end
104
141
  end
105
142
 
106
143
  class U16Field < Field
@@ -122,10 +159,16 @@ module Depix
122
159
  def clean(v)
123
160
  v == BLANK ? nil : v
124
161
  end
162
+
163
+ def validate!(value)
164
+ super(value)
165
+ raise "#{name} value #{value} out of bounds for 16bit unsigned int" if (value < 0 || value >= BLANK)
166
+ end
125
167
  end
126
168
 
127
169
  class R32Field < Field
128
170
  undef :length=, :pattern=
171
+ BLANK = 0xFFFFFFFF
129
172
 
130
173
  def pattern
131
174
  "g"
@@ -169,6 +212,15 @@ module Depix
169
212
  def rtype
170
213
  String
171
214
  end
215
+
216
+ def validate!(value)
217
+ super(value)
218
+ raise "#{value} overflows the #{length} bytes allocated" if !value.nil? && value.length > length
219
+ end
220
+
221
+ def pack(value)
222
+ value.ljust(length, "\000") rescue ("\000" * length)
223
+ end
172
224
  end
173
225
 
174
226
  # Wrapper for an array structure
@@ -198,6 +250,27 @@ module Depix
198
250
  r = (req? ? "- required" : nil)
199
251
  [tpl, desc, r].compact.join(' ')
200
252
  end
253
+
254
+ def validate!(array)
255
+ raise "This value would overflow, #{array.length} elements passed but only #{members.length} fit" unless array.length <= members.length
256
+ raise "This value is required, but the array is empty" if req? && array.empty?
257
+ array.zip(members).map do | v, m |
258
+ m.validate!(v) unless (v.nil? && !m.req?)
259
+ end
260
+ end
261
+
262
+ def pack(values)
263
+ # For members that are present, get values. For members that are missing, fill with null bytes upto length.
264
+ # For values that are nil, skip packing
265
+ members.zip(values).map do |m, v|
266
+ if !m.req? && v.nil?
267
+ raise "#{m} needs to provide length" unless m.length
268
+ "\377" * m.length
269
+ else
270
+ v.respond_to?(:pack) ? v.pack : m.pack(v)
271
+ end
272
+ end.join
273
+ end
201
274
  end
202
275
 
203
276
  # Wrapper for a contained structure
@@ -220,6 +293,15 @@ module Depix
220
293
  def rtype
221
294
  cast
222
295
  end
296
+
297
+ def validate!(value)
298
+ super(value)
299
+ cast.validate!(value) if cast.respond_to?(:validate!) && (!value.nil? || req?)
300
+ end
301
+
302
+ def pack(value)
303
+ cast.pack(value)
304
+ end
223
305
  end
224
306
 
225
307
  # Base class for a struct. Could also be implemented as a module actually
@@ -232,7 +314,14 @@ module Depix
232
314
  def fields
233
315
  @fields ||= []
234
316
  end
235
-
317
+
318
+ # Validate a passed instance
319
+ def validate!(instance)
320
+ fields.each do | f |
321
+ f.validate!(instance.send(f.name)) if f.name
322
+ end
323
+ end
324
+
236
325
  # Define a 4-byte unsigned integer
237
326
  def u32(name, *extras)
238
327
  count, opts = count_and_opts_from(extras)
@@ -273,6 +362,7 @@ module Depix
273
362
  else
274
363
  [Field.send("emit_#{mapped_to}")] * count
275
364
  end
365
+ yield a.members if block_given?
276
366
  fields << a
277
367
  end
278
368
 
@@ -343,6 +433,41 @@ module Depix
343
433
  only([])
344
434
  end
345
435
 
436
+ # Pack the instance of this struct
437
+ def pack(instance, buffer = nil)
438
+
439
+ # Preallocate a buffer just as big as me since we want everything to remain at fixed offsets
440
+ buffer ||= ("\000" * length)
441
+
442
+ # If the instance is nil return pure padding
443
+ return buffer if instance.nil?
444
+
445
+ # Now for the important stuff. For each field that we have, replace a piece at offsets in the buffer
446
+ # with the packed results, skipping fillers
447
+ fields.each_with_index do | f, i |
448
+
449
+ # Skip blanking, we just dont touch it. TODO - test!
450
+ next if f.is_a?(Filler)
451
+
452
+ # Where should we put that value?
453
+ offset = fields[0...i].inject(0){|_, s| _ + s.length }
454
+
455
+ val = instance.send(f.name)
456
+
457
+ # Validate the passed value using the format the field supports
458
+ f.validate!(val)
459
+
460
+ packed = f.pack(val)
461
+
462
+ # Signal offset violation
463
+ raise "Improper length for #{f.name} - packed #{packed.length} bytes but #{f.length} is required to fill the slot" if packed.length != f.length
464
+
465
+ buffer[offset...(offset+f.length)] = packed
466
+ end
467
+ raise "Resulting buffer not the same length, expected #{length} bytes but compued #{buffer.length}" if buffer.length != length
468
+ buffer
469
+ end
470
+
346
471
  private
347
472
 
348
473
  # extract_options! on a diet
@@ -0,0 +1,32 @@
1
+ module Depix
2
+ # Used to edit DPX headers. Create an Editor object and pass the path to the file to it. Change the headers variable to contain the edited
3
+ # DPX headers and call commit!. Note that the DPX header will be overwritten in place - if you want to save another version you need to manage it yourself
4
+ class Editor
5
+
6
+ # Stores the path to file
7
+ attr_reader :path
8
+
9
+ # Stores the Depix::DPX object with headers
10
+ attr_accessor :headers
11
+
12
+ # Create a new editor for the file at path
13
+ def initialize(file_path)
14
+ @path = file_path
15
+ @headers = Depix.from_file(@path)
16
+ end
17
+
18
+ # Save the headers to file at path, overwriting the old ones
19
+ def commit!
20
+ raise "No headers" unless @headers
21
+ raise "Cannot pack LE headers" if @headers.le?
22
+ packed = @headers.class.pack(@headers)
23
+
24
+ # Validate that we can unpack first - what if something went wrong?
25
+ Depix::Reader.new.parse(packed, false)
26
+
27
+ File.open(@path, 'rb+') do | f |
28
+ f.seek(0, IO::SEEK_SET); f.write(packed)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,83 @@
1
+ module Depix
2
+ class Reader
3
+
4
+ # Returns a printable report on all the headers present in the file at the path passed
5
+ def describe_file(path, compact = false)
6
+ header = File.open(path, 'r') { |f| f.read(DPX.length) }
7
+ struct = parse(header, false)
8
+ describe_struct(struct) + describe_synthetics_of_struct(struct)
9
+ end
10
+
11
+ def describe_synthetics_of_struct(struct)
12
+ Synthetics.instance_methods.reject{|m| m.include?('=')}.map do | m |
13
+ [m, struct.send(m)].join(' : ')
14
+ end.unshift("============").unshift("\nSynthetic properties").join("\n")
15
+ end
16
+
17
+ def from_file(path, compact)
18
+ header = File.open(path, 'r') { |f| f.read(DPX.length) }
19
+ begin
20
+ parse(header, compact)
21
+ rescue InvalidHeader => e
22
+ raise InvalidHeader, "Invalid header in file #{path} - #{e.message}"
23
+ end
24
+ end
25
+
26
+ # The hear of Depix
27
+ def parse(data, compact)
28
+ magic = data[0..3]
29
+
30
+ raise InvalidHeader, "No magic bytes found at start" unless %w( SDPX XPDS).include?(magic)
31
+
32
+ struct = compact ? CompactDPX : DPX
33
+
34
+ is_be = (magic == "SDPX")
35
+ version_check = FileInfo.only(:magic, :version)
36
+
37
+ result = begin
38
+ if is_be
39
+ version_check.consume!(data.unpack(version_check.pattern))
40
+ else
41
+ version_check.consume!(data.unpack(make_le(version_check.pattern)))
42
+ end
43
+ rescue ArgumentError
44
+ raise InvalidHeader
45
+ end
46
+
47
+ raise InvalidHeader, "Unknown version tag #{result.version}" unless result.version == "V1.0"
48
+
49
+ template = is_be ? DPX.pattern : make_le(DPX.pattern)
50
+ struct.consume!(data.unpack(struct.pattern))
51
+ end
52
+
53
+ # Describe a filled DPX structure
54
+ def describe_struct(result, pad_offset = 0)
55
+ result.class.fields.inject([]) do | info, field |
56
+ value = result.send(field.name)
57
+ parts = []
58
+ if value
59
+ parts << field.desc if field.desc
60
+ parts << if field.is_a?(InnerField)
61
+ describe_struct(value, pad_offset + 1)
62
+ elsif field.is_a?(ArrayField)
63
+ # Exception for image elements
64
+ value = result.image_elements[0...result.number_elements] if field.name == :image_elements
65
+ value.map { | v | v.is_a?(Dict) ? describe_struct(v, pad_offset + 2) : v }
66
+ else
67
+ value
68
+ end
69
+ end
70
+ if parts.any?
71
+ info << parts.join(' ')
72
+ end
73
+ info
74
+ end.map{|e| (' ' * pad_offset) + e }.join("\n")
75
+ end
76
+
77
+ # Convert an unpack pattern to LE
78
+ def make_le(pattern)
79
+ pattern.gsub(/n/, "v").gsub(/N/, "V").gsub(/g/, "f")
80
+ end
81
+
82
+ end
83
+ end
data/lib/depix/structs.rb CHANGED
@@ -121,17 +121,26 @@ module Depix
121
121
 
122
122
  u32 :pixels_per_line, :desc => 'Pixels per horizontal line', :req => true
123
123
  u32 :lines_per_element, :desc => 'Line count', :req => true
124
- array :image_elements, ImageElement, 8, :desc => "Image elements"
124
+
125
+ array :image_elements, ImageElement, 8, :desc => "Image elements" do | elements |
126
+ elements[0].req = true
127
+ end
128
+
125
129
  char :reserve, 52
130
+
131
+ # Only expose the elements present
132
+ def image_elements #:nodoc:
133
+ @image_elements[0...number_elements]
134
+ end
126
135
  end
127
136
 
128
137
  #:include:DPX_HEADER_STRUCTURE.txt
129
138
  class DPX < Dict
130
- inner :file, FileInfo, :desc => "File information"
131
- inner :image, ImageInfo, :desc => "Image information"
132
- inner :orientation, OrientationInfo, :desc => "Orientation"
133
- inner :film, FilmInfo, :desc => "Film industry info"
134
- inner :television, TelevisionInfo, :desc => "TV industry info"
135
- inner :user, UserInfo, :desc => "User info"
139
+ inner :file, FileInfo, :desc => "File information", :req => true
140
+ inner :image, ImageInfo, :desc => "Image information", :req => true
141
+ inner :orientation, OrientationInfo, :desc => "Orientation", :req => true
142
+ inner :film, FilmInfo, :desc => "Film industry info", :req => true
143
+ inner :television, TelevisionInfo, :desc => "TV industry info", :req => true
144
+ inner :user, UserInfo, :desc => "User info", :req => true
136
145
  end
137
146
  end
data/lib/depix.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'stringio'
2
1
  require 'rubygems'
3
2
  require 'timecode'
4
3
 
@@ -6,14 +5,18 @@ require File.dirname(__FILE__) + '/depix/dict'
6
5
  require File.dirname(__FILE__) + '/depix/structs'
7
6
  require File.dirname(__FILE__) + '/depix/compact_structs'
8
7
  require File.dirname(__FILE__) + '/depix/enums'
8
+ require File.dirname(__FILE__) + '/depix/reader'
9
+ require File.dirname(__FILE__) + '/depix/editor'
10
+
9
11
 
10
12
  module Depix
11
- VERSION = '1.0.3'
13
+ VERSION = '1.0.4'
12
14
 
13
15
  class InvalidHeader < RuntimeError; end
14
16
 
15
17
  # Offers convenience access to a few common attributes bypassing the piecemeal structs
16
18
  module Synthetics
19
+
17
20
  def keycode
18
21
  [film.id, film.type, film.offset, film.prefix, film.count].compact.join(' ')
19
22
  end
@@ -21,13 +24,23 @@ module Depix
21
24
  # Return the flame reel name. The data after the first null byte is not meant to be seen and is used by Flame internally
22
25
  # as it seems
23
26
  def flame_reel
24
- orientation.device.split("\000").shift
27
+ orientation.device.split(0x00.chr).shift
28
+ end
29
+
30
+ # Assign reel name
31
+ def flame_reel=(new_reel)
32
+ orientation.device = new_reel
25
33
  end
26
34
 
27
35
  def time_code
28
36
  Timecode.from_uint(television.time_code) #, film.frame_rate)
29
37
  end
30
38
 
39
+ # Assign frame rate and timecode from a Timecode object
40
+ def time_code=(new_tc)
41
+ television.time_code, film.frame_rate = new_tc.to_uint, new_tc.fps
42
+ end
43
+
31
44
  # Get the name of the transfer function (Linear, Logarithmic, ...)
32
45
  def colorimetric
33
46
  COLORIMETRIC.invert[image.image_elements[0].colorimetric]
@@ -38,6 +51,11 @@ module Depix
38
51
  COMPONENT_TYPE.invert[image.image_elements[0].descriptor]
39
52
  end
40
53
 
54
+ # Aspect in it's traditional repr
55
+ def aspect
56
+ "%.2f" % (orientation.aspect_ratio[0].to_f / orientation.aspect_ratio[1].to_f)
57
+ end
58
+
41
59
  # Is this DPX file little-endian? This would be an exception, but still useful
42
60
  def le?
43
61
  file.magic == 'XPDS'
@@ -65,78 +83,9 @@ module Depix
65
83
  Reader.new.describe_file(path, compact)
66
84
  end
67
85
 
68
- class Reader
69
-
70
- # Returns a printable report on all the headers present in the file at the path passed
71
- def describe_file(path, compact = false)
72
- header = File.open(path, 'r') { |f| f.read(DPX.length) }
73
- describe_struct(parse(header, false))
74
- end
75
-
76
- def from_file(path, compact)
77
- header = File.open(path, 'r') { |f| f.read(DPX.length) }
78
- begin
79
- parse(header, compact)
80
- rescue InvalidHeader => e
81
- raise InvalidHeader, "Invalid header in file #{path}"
82
- end
83
- end
84
-
85
- # The hear of Depix
86
- def parse(data, compact)
87
- magic = data[0..3]
88
-
89
- raise InvalidHeader unless %w( SDPX XPDS).include?(magic)
90
-
91
- struct = compact ? CompactDPX : DPX
92
-
93
- is_be = (magic == "SDPX")
94
- version_check = FileInfo.only(:magic, :version)
95
-
96
- result = begin
97
- if is_be
98
- version_check.consume!(data.unpack(version_check.pattern))
99
- else
100
- version_check.consume!(data.unpack(make_le(version_check.pattern)))
101
- end
102
- rescue ArgumentError
103
- raise InvalidHeader
104
- end
105
-
106
- raise InvalidHeader unless result.version == "V1.0"
107
-
108
- template = is_be ? DPX.pattern : make_le(DPX.pattern)
109
- struct.consume!(data.unpack(struct.pattern))
110
- end
111
-
112
- # Describe a filled DPX structure
113
- def describe_struct(result, pad_offset = 0)
114
- result.class.fields.inject([]) do | info, field |
115
- value = result.send(field.name)
116
- parts = []
117
- if value
118
- parts << field.desc if field.desc
119
- parts << if field.is_a?(InnerField)
120
- describe_struct(value, pad_offset + 1)
121
- elsif field.is_a?(ArrayField)
122
- # Exception for image elements
123
- value = result.image_elements[0...result.number_elements] if field.name == :image_elements
124
- value.map { | v | v.is_a?(Dict) ? describe_struct(v, pad_offset + 2) : v }
125
- else
126
- value
127
- end
128
- end
129
- if parts.any?
130
- info << parts.join(' ')
131
- end
132
- info
133
- end.map{|e| (' ' * pad_offset) + e }.join("\n")
134
- end
135
-
136
- # Convert an unpack pattern to LE
137
- def make_le(pattern)
138
- pattern.gsub(/n/, "v").gsub(/N/, "V").gsub(/g/, "f")
139
- end
140
-
86
+ # Return a formatted description of the DPX file at path, showing only synthetic attributes
87
+ def self.describe_brief(path)
88
+ Reader.new.describe_synthetics_of_struct(from_file(path))
141
89
  end
90
+
142
91
  end
data/test/test_depix.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require File.dirname(__FILE__) + '/../lib/depix'
2
2
  require 'test/unit'
3
3
 
4
+ SAMPLE_DPX = File.dirname(__FILE__) + '/samples/E012_P001_L000002_lin.0001.dpx'
5
+
4
6
  class ReaderTest < Test::Unit::TestCase
5
7
 
6
- SAMPLE_DPX = File.dirname(__FILE__) + '/samples/E012_P001_L000002_lin.0001.dpx'
7
-
8
8
  def test_parsed_properly
9
9
  file = SAMPLE_DPX
10
10
  parsed = Depix.from_file(file)
@@ -69,6 +69,7 @@ class ReaderTest < Test::Unit::TestCase
69
69
  assert_equal :RGB, parsed.component_type
70
70
  assert_equal :Linear, parsed.colorimetric
71
71
  assert_equal "E012", parsed.flame_reel
72
+ assert_equal "1.33", parsed.aspect
72
73
  end
73
74
 
74
75
  def test_parsed_properly_using_compact_structs
@@ -84,6 +85,17 @@ class ReaderTest < Test::Unit::TestCase
84
85
  end
85
86
  end
86
87
 
88
+ def test_packing
89
+ original_header = File.read(SAMPLE_DPX)[0...Depix::DPX.length]
90
+
91
+ assert_nothing_raised do
92
+ dpx = Depix.from_string(original_header)
93
+ packed = Depix::DPX.pack(dpx, original_header.dup)
94
+
95
+ dpx2 = Depix.from_string(packed)
96
+ end
97
+ end
98
+
87
99
  def test_parsing_something_else_should_raise
88
100
  s = "Mary had a little lamb"
89
101
  assert_raise(Depix::InvalidHeader) { Depix.from_string(s) }
@@ -95,4 +107,29 @@ class ReaderTest < Test::Unit::TestCase
95
107
  assert_raise(Depix::InvalidHeader) { Depix.from_string(s) }
96
108
 
97
109
  end
110
+ end
111
+
112
+ class EditorTest < Test::Unit::TestCase
113
+ def test_instantiation
114
+ e = Depix::Editor.new(SAMPLE_DPX)
115
+ assert_not_nil e
116
+ assert_equal SAMPLE_DPX, e.path
117
+ assert_not_nil e.headers
118
+ end
119
+
120
+ def test_commit
121
+ temp_path = SAMPLE_DPX + ".test"
122
+ begin
123
+ FileUtils.cp(SAMPLE_DPX, temp_path)
124
+ e = Depix::Editor.new(temp_path)
125
+ e.headers.orientation.device = "E013"
126
+
127
+ assert_nothing_raised { e.commit! }
128
+
129
+ re_read = Depix.from_file(temp_path)
130
+ assert_equal "E013", re_read.orientation.device
131
+ ensure
132
+ File.unlink(temp_path)
133
+ end
134
+ end
98
135
  end
data/test/test_dict.rb CHANGED
@@ -3,6 +3,28 @@ require 'test/unit'
3
3
 
4
4
  include Depix
5
5
 
6
+ class BogusError < RuntimeError; end
7
+
8
+ class AlwaysInvalidField < Field
9
+ def validate!(value)
10
+ raise BogusError, "Never valid"
11
+ end
12
+
13
+ def pack(some_value)
14
+ raise BogusError, "Will not pack"
15
+ end
16
+ end
17
+
18
+ class AlwaysInvalidStruct
19
+ def self.validate!(value)
20
+ raise BogusError, "Never valid"
21
+ end
22
+
23
+ def self.pack(instance)
24
+ raise BogusError, "Will not pack"
25
+ end
26
+ end
27
+
6
28
  module FieldConformity
7
29
  def conform_field!(f)
8
30
  assert_respond_to f, :name
@@ -12,6 +34,8 @@ module FieldConformity
12
34
  assert_respond_to f, :req?
13
35
  assert_respond_to f, :rtype
14
36
  assert_respond_to f, :explain
37
+ assert_respond_to f, :validate!
38
+ assert_respond_to f, :pack
15
39
  end
16
40
 
17
41
  def assert_method_removed(f, method)
@@ -99,24 +123,24 @@ class TestField < Test::Unit::TestCase
99
123
  assert_equal [2,3], ar
100
124
  end
101
125
 
102
- def test_consume_for_inner_field
103
- catcher = Class.new do
104
- def self.consume!(arg)
105
- raise RuntimeError if arg == ["julik"]
106
- end
107
- end
108
-
109
- f = InnerField.new :cast => catcher
110
- assert_respond_to f, :consume!
126
+ def test_validate
127
+ f = Field.new
128
+ f.rtype = self.class
129
+ assert_nothing_raised { f.validate! nil }
111
130
 
112
- assert_raise(RuntimeError) { f.consume!(["julik"]) }
131
+ assert_raise(RuntimeError) { f.validate! "boo" }
132
+ assert_nothing_raised { f.validate! self }
113
133
  end
114
134
 
115
- def test_consume_for_array
116
- f = ArrayField.new :members => [Field.new, Field.new]
117
- assert_respond_to f, :consume!
118
-
119
- assert_equal [1,2], f.consume!([1,2])
135
+ def test_validate_if_required
136
+ f = Field.new
137
+ f.req = true
138
+ assert_raise(RuntimeError) { f.validate! nil }
139
+ end
140
+
141
+ def test_pack_raises
142
+ f = Field.new
143
+ assert_raise(RuntimeError) { f.pack("foo")}
120
144
  end
121
145
  end
122
146
 
@@ -159,6 +183,59 @@ class TestArrayField < Test::Unit::TestCase
159
183
  assert_equal 3, f.length
160
184
  assert_equal "CC2", f.pattern
161
185
  end
186
+
187
+ def test_consume
188
+ f = ArrayField.new :members => [Field.new, Field.new]
189
+ assert_respond_to f, :consume!
190
+
191
+ assert_equal [1,2], f.consume!([1,2])
192
+ end
193
+
194
+ def test_validate
195
+ f = ArrayField.new :members => [Field.new(:rtype => self.class)]
196
+
197
+ # Overflow
198
+ assert_raise(RuntimeError) { f.validate!([nil, nil]) }
199
+
200
+ # Just empty
201
+ assert_nothing_raised { f.validate!([]) }
202
+
203
+ # Nil vaue
204
+ assert_nothing_raised { f.validate!([nil]) }
205
+
206
+ # type cast
207
+ assert_raise(RuntimeError) { f.validate!(["nil"]) }
208
+
209
+ assert_nothing_raised { f.validate!([self]) }
210
+ end
211
+
212
+ def test_validate_fails_with_empty_array_and_required_field
213
+ f = ArrayField.new :members => [Field.new(:rtype => self.class)], :req => true
214
+ assert_raise(RuntimeError) { f.validate!([]) }
215
+ end
216
+
217
+ def test_validate_does_not_validate_inner_structure_if_no_value_present_and_field_is_not_required
218
+ f = ArrayField.new :members => [AlwaysInvalidField.new]
219
+ assert_nothing_raised { f.validate! [nil] }
220
+ end
221
+
222
+
223
+ def test_pack_tries_to_pack_inner_structures
224
+ f = ArrayField.new :members => [AlwaysInvalidField.new]
225
+ assert_raise(BogusError) { f.pack([1, 2]) }
226
+ end
227
+
228
+ def test_pack_pads_properly
229
+ f = ArrayField.new :members => [U32Field.new, R32Field.new, R32Field.new]
230
+ assert_equal "\000\000\000\001@\000\000\000\377\377\377\377", f.pack([1.0, 2.0])
231
+ assert_equal f.length, f.pack([1.0, 2.0]).length
232
+ end
233
+
234
+ def test_does_not_try_to_pack_nil_values
235
+ f = ArrayField.new(:members => [AlwaysInvalidField.new(:length => 2)])
236
+ assert_equal "\377\377", f.pack([])
237
+ end
238
+
162
239
  end
163
240
 
164
241
  class TestInnerField < Test::Unit::TestCase
@@ -194,6 +271,34 @@ class TestInnerField < Test::Unit::TestCase
194
271
  casted = InnerField.new(:cast => c)
195
272
  assert_equal c, casted.rtype
196
273
  end
274
+
275
+ def test_consume
276
+ catcher = Class.new do
277
+ def self.consume!(arg)
278
+ raise RuntimeError if arg == ["julik"]
279
+ end
280
+ end
281
+
282
+ f = InnerField.new :cast => catcher
283
+ assert_respond_to f, :consume!
284
+
285
+ assert_raise(RuntimeError) { f.consume!(["julik"]) }
286
+ end
287
+
288
+ def test_validate_with_nil_and_no_requirement
289
+ f = InnerField.new :cast => AlwaysInvalidStruct, :req => true
290
+ assert_raise(RuntimeError) { f.validate!(nil) }
291
+ end
292
+
293
+ def test_validate
294
+ f = InnerField.new :cast => AlwaysInvalidStruct
295
+ assert_raise(BogusError) { f.validate!(AlwaysInvalidStruct.new) }
296
+ end
297
+
298
+ def test_pack_tries_to_pack_inner_structures
299
+ f = InnerField.new :cast => AlwaysInvalidStruct
300
+ assert_raise(BogusError) { f.pack(AlwaysInvalidStruct.new) }
301
+ end
197
302
  end
198
303
 
199
304
  class TestWideIntField < Test::Unit::TestCase
@@ -209,6 +314,23 @@ class TestWideIntField < Test::Unit::TestCase
209
314
  assert_equal 66, f.clean(66)
210
315
  assert_equal nil, f.clean(0xFFFFFFFF)
211
316
  end
317
+
318
+ def test_validate
319
+ f = U32Field.new
320
+
321
+ assert_nothing_raised { f.validate! 8 }
322
+ assert_nothing_raised { f.validate! 0 }
323
+ assert_nothing_raised { f.validate! 65536 }
324
+ assert_raise(RuntimeError) { f.validate!(0xFFFFFFFF) }
325
+ assert_nothing_raised { f.validate!(0xFFFFFFFF - 1) }
326
+
327
+ end
328
+
329
+ def test_pack
330
+ w = U32Field.new
331
+ assert_equal "\000\000\000\036", w.pack(30)
332
+ assert_equal "\377\377\377\377", w.pack(nil)
333
+ end
212
334
  end
213
335
 
214
336
  class TestCharField < Test::Unit::TestCase
@@ -240,7 +362,25 @@ class TestCharField < Test::Unit::TestCase
240
362
  assert_equal nil, f.clean("\0\0\0\0\0\0")
241
363
  assert_equal nil, f.clean("\0\0\0\377\377\0\0\0")
242
364
  assert_equal "foo\0foo", f.clean("\0\0foo\0foo\0")
243
-
365
+ end
366
+
367
+ def test_char_field_validates_overflow
368
+ f = CharField.new :length => 2
369
+ assert_raise(RuntimeError) { f.validate!("xxx")}
370
+ assert_nothing_raised { f.validate!("xx")}
371
+ assert_nothing_raised { f.validate!(nil)}
372
+ end
373
+
374
+ def test_char_field_validates_required_with_nil
375
+ f = CharField.new :length => 2, :req => true
376
+ assert_raise(RuntimeError) { f.validate!(nil)}
377
+ end
378
+
379
+ def test_char_field_packs
380
+ f = CharField.new(:length => 6)
381
+ assert_equal "xx\000\000\000\000", f.pack("xx")
382
+ assert_equal "\000\000\000\000\000\000", f.pack("")
383
+ assert_equal "\000\000\000\000\000\000", f.pack(nil)
244
384
  end
245
385
  end
246
386
 
@@ -261,6 +401,12 @@ class TestFloatField < Test::Unit::TestCase
261
401
  assert_equal Float, f.rtype
262
402
  assert_equal nil, f.clean(the_nan)
263
403
  end
404
+
405
+ def test_pack
406
+ w = R32Field.new
407
+ assert_equal "@fff", w.pack(3.6)
408
+ assert_equal "O\200\000\000", w.pack(nil)
409
+ end
264
410
  end
265
411
 
266
412
  class TestSmallintField < Test::Unit::TestCase
@@ -272,7 +418,6 @@ class TestSmallintField < Test::Unit::TestCase
272
418
 
273
419
  assert_method_removed(f, :pattern=)
274
420
  assert_method_removed(f, :length=)
275
-
276
421
  end
277
422
 
278
423
  def test_smallint_operation
@@ -285,11 +430,18 @@ class TestSmallintField < Test::Unit::TestCase
285
430
 
286
431
  def test_smallint_clean
287
432
  f = U8Field.new
288
-
289
433
  assert_equal nil, f.clean(0xFF)
290
434
  assert_equal 10, f.clean(10)
291
435
  end
292
436
 
437
+ def test_validate
438
+ f = U8Field.new
439
+ assert_nothing_raised { f.validate! 8 }
440
+ assert_nothing_raised { f.validate! 0 }
441
+ assert_raise(RuntimeError) { f.validate!( -1 ) }
442
+ assert_raise(RuntimeError) { f.validate!( 255) }
443
+ assert_raise(RuntimeError) { f.validate!( 256) }
444
+ end
293
445
  end
294
446
 
295
447
  class TestDoubleField < Test::Unit::TestCase
@@ -317,6 +469,15 @@ class TestDoubleField < Test::Unit::TestCase
317
469
  assert_equal nil, f.clean(0xFFFF)
318
470
  assert_equal 10, f.clean(10)
319
471
  end
472
+
473
+ def test_validate
474
+ f = U16Field.new
475
+ assert_nothing_raised { f.validate! 8 }
476
+ assert_nothing_raised { f.validate! 0 }
477
+ assert_raise(RuntimeError) { f.validate!( -1) }
478
+ assert_raise(RuntimeError) { f.validate!( 65535) }
479
+ assert_raise(RuntimeError) { f.validate!( 65536) }
480
+ end
320
481
  end
321
482
 
322
483
  class TestFillerField < Test::Unit::TestCase
@@ -344,7 +505,10 @@ class TestFillerField < Test::Unit::TestCase
344
505
  data.freeze
345
506
  assert_nothing_raised { Filler.new(:length => 1).consume(data) }
346
507
  end
347
-
508
+
509
+ def test_pack_raises
510
+ assert_raise(RuntimeError) { Filler.new(:length => 3).pack('xxx') }
511
+ end
348
512
  end
349
513
 
350
514
  class TestFieldEmit < Test::Unit::TestCase
@@ -401,6 +565,12 @@ class TestDict < Test::Unit::TestCase
401
565
  assert_equal [], dict_class.fields
402
566
  end
403
567
 
568
+ def test_dict_responds_to_validate
569
+ dict_class = Class.new(Dict)
570
+ assert_respond_to dict_class, :validate!
571
+ one = dict_class.new
572
+ end
573
+
404
574
  def test_dict_fields_array_not_class_shared
405
575
  d1, d2 = (0..1).map{|_| Class.new(Dict) }
406
576
 
@@ -434,7 +604,27 @@ class TestDict < Test::Unit::TestCase
434
604
  assert_equal 'A1A1', c.pattern
435
605
  assert_equal 2, c.length
436
606
  end
607
+
608
+ def test_dict_does_not_validate_inner_nil
609
+ wrapper_class = Class.new(Dict) do
610
+ u32 :bigint
611
+ inner :invalid, AlwaysInvalidStruct
612
+ end
613
+ struct = wrapper_class.new
614
+ assert_nothing_raised { wrapper_class.validate!(struct) }
615
+ end
437
616
 
617
+ def test_dict_calls_validate
618
+ wrapper_class = Class.new(Dict) do
619
+ u32 :bigint
620
+ inner :invalid, AlwaysInvalidStruct, :req => true
621
+ end
622
+
623
+ struct = wrapper_class.new
624
+ struct.invalid = AlwaysInvalidStruct.new
625
+
626
+ assert_raise(BogusError) { wrapper_class.validate!(struct) }
627
+ end
438
628
  end
439
629
 
440
630
  class TestDictConsume < Test::Unit::TestCase
@@ -449,6 +639,7 @@ class TestDictConsume < Test::Unit::TestCase
449
639
  assert_equal "a", result.foo
450
640
  assert_equal "b", result.bar
451
641
  end
642
+
452
643
  end
453
644
 
454
645
  class TestDictEmitDSL < Test::Unit::TestCase
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: julik-depix
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-22 00:00:00 -08:00
12
+ date: 2008-12-26 00:00:00 -08:00
13
13
  default_executable: depix-describe
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -43,15 +43,18 @@ files:
43
43
  - lib/depix.rb
44
44
  - lib/depix/struct_explainer.rb
45
45
  - lib/depix/structs.rb
46
+ - lib/depix/benchmark.rb
46
47
  - lib/depix/compact_structs.rb
47
48
  - lib/depix/enums.rb
48
49
  - lib/depix/dict.rb
50
+ - lib/depix/reader.rb
51
+ - lib/depix/editor.rb
52
+ - test/test_dict.rb
49
53
  - test/test_depix.rb
50
54
  - test/samples/E012_P001_L000002_lin.0001.dpx
51
55
  - test/samples/E012_P001_L000002_lin.0002.dpx
52
56
  - test/samples/E012_P001_L000002_log.0001.dpx
53
57
  - test/samples/E012_P001_L000002_log.0002.dpx
54
- - test/test_dict.rb
55
58
  has_rdoc: true
56
59
  homepage: http://wiretap.rubyforge.org/depix
57
60
  post_install_message: