binary_plist 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog.markdown CHANGED
@@ -1,3 +1,9 @@
1
+ ### 0.0.5 (Jul 26, 2010)
2
+
3
+ * Added Railtie
4
+ * Added support for `:plist` with `respond_with`
5
+ * Added `to_plist` method to supported classes
6
+
1
7
  ### 0.0.4 (Jul 26, 2010)
2
8
 
3
9
  * Fixed ActiveSupport dependency
data/Readme.markdown CHANGED
@@ -54,4 +54,8 @@ You can also use the more flexible syntax:
54
54
  // Etc...
55
55
  }
56
56
 
57
+ ## Thanks
58
+
59
+ The encoder is largely based on [Apple Binary Property List serializer](http://gist.github.com/303378).
60
+
57
61
  Copyright (c) 2010 Sam Soffes, released under the MIT license
data/init.rb CHANGED
@@ -1,11 +1 @@
1
- require "binary_plist"
2
-
3
- Mime::Type.register BinaryPlist::MIME_TYPE, :bplist
4
-
5
- ActionController::Renderers.add :bplist do |data, options|
6
- # TODO: Make this less hacky
7
- data = ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(data, options))
8
-
9
- self.content_type ||= Mime::BPLIST
10
- self.response_body = BinaryPlist.encode(data)
11
- end
1
+ require File.join(File.dirname(__FILE__), "lib", "binary_plist")
data/lib/binary_plist.rb CHANGED
@@ -1,174 +1,8 @@
1
- # Based on Apple Binary Property List serializer - http://gist.github.com/303378
2
- require 'iconv'
1
+ require "binary_plist/encoding"
2
+ require "binary_plist/to_plist"
3
+ require "binary_plist/railtie"
4
+ require "iconv"
3
5
 
4
6
  module BinaryPlist
5
-
6
- # Very generic type for now
7
- MIME_TYPE = 'application/octet-stream'
8
-
9
- # Difference between Apple and UNIX timestamps
10
- DATE_EPOCH_OFFSET_APPLE_UNIX = 978307200
11
-
12
- # Text encoding
13
- INPUT_TEXT_ENCODING = 'UTF-8'
14
- PLIST_TEXT_ENCODING = 'UTF-16BE'
15
-
16
- # For marking strings as binary data which will be decoded as a CFData object
17
- CFData = Struct.new(:data)
18
-
19
- # Convert a Ruby data structure into a binary property list file.
20
- # Works as you'd expect. Integers are limited to 4 bytes, even though the format implies longer values can be written.
21
- # Strings are assumed to be in UTF-8 format. Symbols are written as strings.
22
- def self.encode object
23
- write "", object
24
- end
25
-
26
- # Alternative interface which writes data to the out object using <<
27
- def self.write out, object
28
- # Find out how many objects there are, so we know how big the references are
29
- count = count_objects(object)
30
- ref_format, ref_size = int_format_and_size(count)
31
-
32
- # Now serialize all the objects
33
- values = Array.new
34
- append_values(object, values, ref_format)
35
-
36
- # Write header, then the values, calculating offsets as they're written
37
- out << 'bplist00'
38
- offset = 8
39
- offsets = Array.new
40
- values.each do |v|
41
- offsets << offset
42
- out << v
43
- offset += v.length
44
- end
45
-
46
- # How big should the offset ints be?
47
- # Decoder gets upset if the size can't fit the entire file, even if it's not strictly needed, so add the length of the last value.
48
- offset_format, offset_size = int_format_and_size(offsets.last + values.last.length)
49
-
50
- # Write the offsets
51
- out << offsets.pack(offset_format)
52
-
53
- # Write trailer
54
- out << [0, 0, offset_size, ref_size, 0, values.length, 0, 0, 0, offset].pack("NnCCNNNNNN")
55
- end
56
-
57
- private
58
-
59
- def self.count_objects object
60
- case object
61
- when Array
62
- object.inject(1) { |sum, x| sum + count_objects(x) }
63
-
64
- when Hash
65
- # Note: Assumes that the keys aren't a Hash or Array
66
- object.length + count_objects(object.values)
67
- else
68
- 1
69
- end
70
- end
71
-
72
- def self.append_values object, values, ref_format
73
- case object
74
- when nil
75
- # raise "Can't store a nil in a binary plist. While the format supports it, decoders don't like it." # values << "\x00"
76
- # Instead of storing actual nil, store an empty string
77
- append_values("", values, ref_format)
78
-
79
- when false
80
- values << "\x08"
81
-
82
- when true
83
- values << "\x09"
84
-
85
- when Integer
86
- raise "Integer out of range to write in binary plist: #{objet}" if object < -2147483648 || object > 0x7FFFFFFF
87
- values << packed_int(object)
88
-
89
- when Float
90
- values << "\x23#{[object].pack("d").reverse}"
91
-
92
- when Symbol
93
- append_values(object.to_s, values, ref_format)
94
-
95
- when String
96
- if object =~ /[\x80-\xff]/
97
- # Has high bits set, so is UTF-8 and must be reencoded for the plist file
98
- c = Iconv.iconv(PLIST_TEXT_ENCODING, INPUT_TEXT_ENCODING, object).join
99
- values << objhdr_with_length(0x60, c.length / 2) + c
100
- else
101
- # Just ASCII
102
- values << objhdr_with_length(0x50, object.length) + object
103
- end
104
-
105
- when CFData
106
- o = objhdr_with_length(0x40, object.data.length)
107
- o << object.data
108
- values << o
109
-
110
- when Time
111
- v = object.getutc.to_f - DATE_EPOCH_OFFSET_APPLE_UNIX
112
- values << "\x33#{[v].pack("d").reverse}"
113
-
114
- when Hash
115
- o = objhdr_with_length(0xd0, object.length)
116
- values << o # now, so we get the refs of other objects right
117
- ks = Array.new
118
- vs = Array.new
119
- object.each do |k,v|
120
- ks << values.length
121
- append_values(k, values, ref_format)
122
- vs << values.length
123
- append_values(v, values, ref_format)
124
- end
125
- o << ks.pack(ref_format)
126
- o << vs.pack(ref_format)
127
-
128
-
129
- when Array
130
- o = objhdr_with_length(0xa0, object.length)
131
- values << o # now, so we get the refs of other objects right
132
- refs = Array.new
133
- object.each do |e|
134
- refs << values.length # index in array of object we're about to write
135
- append_values(e, values, ref_format)
136
- end
137
- o << refs.pack(ref_format)
138
-
139
- else
140
- raise "Couldn't serialize value of class #{data.class.name}"
141
- end
142
- end
143
-
144
- def self.int_format_and_size(i)
145
- if i > 0xffff
146
- ['N*', 4]
147
- elsif i > 0xff
148
- ['n*', 2]
149
- else
150
- ['C*', 1]
151
- end
152
- end
153
-
154
- def self.packed_int integer
155
- if integer < 0
156
- # Need to use 64 bits for negative numbers.
157
- [0x13, 0xffffffff, integer].pack("CNN")
158
- elsif integer > 0xffff
159
- [0x12, integer].pack("CN")
160
- elsif integer > 0xff
161
- [0x11, integer].pack("Cn")
162
- else
163
- [0x10, integer].pack("CC")
164
- end
165
- end
166
-
167
- def self.objhdr_with_length id, length
168
- if length < 0xf
169
- (id + length).chr
170
- else
171
- (id + 0xf).chr + packed_int(length)
172
- end
173
- end
7
+ MIME_TYPE = "application/octet-stream"
174
8
  end
@@ -0,0 +1,176 @@
1
+ require "iconv"
2
+
3
+ module BinaryPlist
4
+
5
+ def self.encode value, options = nil
6
+ Encoding.encode value
7
+ end
8
+
9
+ module Encoding
10
+ # Difference between Apple and UNIX timestamps
11
+ DATE_EPOCH_OFFSET_APPLE_UNIX = 978307200
12
+
13
+ # Text encoding
14
+ INPUT_TEXT_ENCODING = 'UTF-8'
15
+ PLIST_TEXT_ENCODING = 'UTF-16BE'
16
+
17
+ # For marking strings as binary data which will be decoded as a CFData object
18
+ CFData = Struct.new(:data)
19
+
20
+ # Convert a Ruby data structure into a binary property list file.
21
+ # Works as you'd expect. Integers are limited to 4 bytes, even though the format implies longer values can be written.
22
+ # Strings are assumed to be in UTF-8 format. Symbols are written as strings.
23
+ def self.encode object
24
+ write "", object
25
+ end
26
+
27
+ # Alternative interface which writes data to the out object using <<
28
+ def self.write out, object
29
+ # Find out how many objects there are, so we know how big the references are
30
+ count = count_objects(object)
31
+ ref_format, ref_size = int_format_and_size(count)
32
+
33
+ # Now serialize all the objects
34
+ values = Array.new
35
+ append_values(object, values, ref_format)
36
+
37
+ # Write header, then the values, calculating offsets as they're written
38
+ out << 'bplist00'
39
+ offset = 8
40
+ offsets = Array.new
41
+ values.each do |v|
42
+ offsets << offset
43
+ out << v
44
+ offset += v.length
45
+ end
46
+
47
+ # How big should the offset ints be?
48
+ # Decoder gets upset if the size can't fit the entire file, even if it's not strictly needed, so add the length of the last value.
49
+ offset_format, offset_size = int_format_and_size(offsets.last + values.last.length)
50
+
51
+ # Write the offsets
52
+ out << offsets.pack(offset_format)
53
+
54
+ # Write trailer
55
+ out << [0, 0, offset_size, ref_size, 0, values.length, 0, 0, 0, offset].pack("NnCCNNNNNN")
56
+ end
57
+
58
+ private
59
+
60
+ def self.count_objects object
61
+ case object
62
+ when Array
63
+ object.inject(1) { |sum, x| sum + count_objects(x) }
64
+
65
+ when Hash
66
+ # Note: Assumes that the keys aren't a Hash or Array
67
+ object.length + count_objects(object.values)
68
+ else
69
+ 1
70
+ end
71
+ end
72
+
73
+ def self.append_values object, values, ref_format
74
+ case object
75
+ when nil
76
+ # raise "Can't store a nil in a binary plist. While the format supports it, decoders don't like it." # values << "\x00"
77
+ # Instead of storing actual nil, store an empty string
78
+ append_values("", values, ref_format)
79
+
80
+ when false
81
+ values << "\x08"
82
+
83
+ when true
84
+ values << "\x09"
85
+
86
+ when Integer
87
+ raise "Integer out of range to write in binary plist: #{objet}" if object < -2147483648 || object > 0x7FFFFFFF
88
+ values << packed_int(object)
89
+
90
+ when Float
91
+ values << "\x23#{[object].pack("d").reverse}"
92
+
93
+ when Symbol
94
+ append_values(object.to_s, values, ref_format)
95
+
96
+ when String
97
+ if object =~ /[\x80-\xff]/
98
+ # Has high bits set, so is UTF-8 and must be reencoded for the plist file
99
+ c = Iconv.iconv(PLIST_TEXT_ENCODING, INPUT_TEXT_ENCODING, object).join
100
+ values << objhdr_with_length(0x60, c.length / 2) + c
101
+ else
102
+ # Just ASCII
103
+ values << objhdr_with_length(0x50, object.length) + object
104
+ end
105
+
106
+ when CFData
107
+ o = objhdr_with_length(0x40, object.data.length)
108
+ o << object.data
109
+ values << o
110
+
111
+ when Time
112
+ v = object.getutc.to_f - DATE_EPOCH_OFFSET_APPLE_UNIX
113
+ values << "\x33#{[v].pack("d").reverse}"
114
+
115
+ when Hash
116
+ o = objhdr_with_length(0xd0, object.length)
117
+ values << o # now, so we get the refs of other objects right
118
+ ks = Array.new
119
+ vs = Array.new
120
+ object.each do |k,v|
121
+ ks << values.length
122
+ append_values(k, values, ref_format)
123
+ vs << values.length
124
+ append_values(v, values, ref_format)
125
+ end
126
+ o << ks.pack(ref_format)
127
+ o << vs.pack(ref_format)
128
+
129
+
130
+ when Array
131
+ o = objhdr_with_length(0xa0, object.length)
132
+ values << o # now, so we get the refs of other objects right
133
+ refs = Array.new
134
+ object.each do |e|
135
+ refs << values.length # index in array of object we're about to write
136
+ append_values(e, values, ref_format)
137
+ end
138
+ o << refs.pack(ref_format)
139
+
140
+ else
141
+ raise "Couldn't serialize value of class #{data.class.name}"
142
+ end
143
+ end
144
+
145
+ def self.int_format_and_size(i)
146
+ if i > 0xffff
147
+ ['N*', 4]
148
+ elsif i > 0xff
149
+ ['n*', 2]
150
+ else
151
+ ['C*', 1]
152
+ end
153
+ end
154
+
155
+ def self.packed_int integer
156
+ if integer < 0
157
+ # Need to use 64 bits for negative numbers.
158
+ [0x13, 0xffffffff, integer].pack("CNN")
159
+ elsif integer > 0xffff
160
+ [0x12, integer].pack("CN")
161
+ elsif integer > 0xff
162
+ [0x11, integer].pack("Cn")
163
+ else
164
+ [0x10, integer].pack("CC")
165
+ end
166
+ end
167
+
168
+ def self.objhdr_with_length id, length
169
+ if length < 0xf
170
+ (id + length).chr
171
+ else
172
+ (id + 0xf).chr + packed_int(length)
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,30 @@
1
+ require "binary_plist"
2
+
3
+ module BinaryPlist
4
+ autoload :BinaryPlistResponder, 'binary_plist/responder'
5
+
6
+ if defined? Rails::Railtie
7
+ require "rails"
8
+ class Railtie < Rails::Railtie
9
+ initializer "binary_plist.add_plist_responder" do
10
+ ActiveSupport.on_load :active_record do
11
+ BinaryPlist::Railtie.insert
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ class Railtie
18
+ def self.insert
19
+ Mime::Type.register BinaryPlist::MIME_TYPE, :plist
20
+
21
+ ActionController::Renderers.add :plist do |data, options|
22
+ # TODO: Make this less hacky
23
+ data = ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(data, options))
24
+
25
+ self.content_type ||= Mime::PLIST
26
+ self.response_body = BinaryPlist.encode(data)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ module BinaryPlist
2
+ module BinaryPlistResponder
3
+ def to_format
4
+ BinaryPlist.encode resource
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # TODO: Make supported classes a constant and define CFData
2
+
3
+ [NilClass, FalseClass, TrueClass, Integer, Float, Symbol, String, Time, Hash, Array].each do |klass|
4
+ klass.class_eval <<-RUBY, __FILE__, __LINE__
5
+ def to_plist(options = nil)
6
+ BinaryPlist.encode(self, options)
7
+ end
8
+ RUBY
9
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: binary_plist
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 4
10
- version: 0.0.4
9
+ - 5
10
+ version: 0.0.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sam Soffes
@@ -58,6 +58,10 @@ extensions: []
58
58
  extra_rdoc_files: []
59
59
 
60
60
  files:
61
+ - lib/binary_plist/encoding.rb
62
+ - lib/binary_plist/railtie.rb
63
+ - lib/binary_plist/responder.rb
64
+ - lib/binary_plist/to_plist.rb
61
65
  - lib/binary_plist.rb
62
66
  - Changelog.markdown
63
67
  - LICENSE