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 +6 -0
- data/Readme.markdown +4 -0
- data/init.rb +1 -11
- data/lib/binary_plist.rb +5 -171
- data/lib/binary_plist/encoding.rb +176 -0
- data/lib/binary_plist/railtie.rb +30 -0
- data/lib/binary_plist/responder.rb +7 -0
- data/lib/binary_plist/to_plist.rb +9 -0
- metadata +7 -3
data/Changelog.markdown
CHANGED
data/Readme.markdown
CHANGED
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
|
-
|
2
|
-
require
|
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,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:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
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
|