binary_plist 0.0.4 → 0.0.5

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/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