kballard-osx-plist 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ == 1.0 / 2008-04-25
2
+ * First public release
@@ -0,0 +1,10 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ ext/plist/extconf.rb
6
+ ext/plist/plist.c
7
+ plist.gemspec
8
+ test/fixtures/xml_plist
9
+ test/suite.rb
10
+ test/test_plist.rb
@@ -0,0 +1,83 @@
1
+ == osx/plist
2
+ by Kevin Ballard
3
+ http://github.com/kballard/plist
4
+
5
+ == DESCRIPTION:
6
+
7
+ osx/plist is a Ruby library for manipulating Property Lists natively using the built-in support in OS X.
8
+
9
+ == REQUIREMENTS:
10
+
11
+ * CoreFoundation (i.e. Mac OS X)
12
+
13
+ == INSTALL:
14
+
15
+ $ gem sources -a http://gems.github.com/ (you only need to do this once)
16
+ $ gem install kballard-plist
17
+
18
+ == SOURCE:
19
+
20
+ osx/plist's git repo is available on GitHub, which can be browsed at:
21
+
22
+ http://github.com/kballard/plist
23
+
24
+ and cloned from:
25
+
26
+ git://github.com/kballard/plist.git
27
+
28
+ == USAGE:
29
+
30
+ One new module is provided, named OSX::PropertyList. It has the following 2 methods:
31
+
32
+ ==== OSX::PropertyList.load(input, format = false)
33
+
34
+ Loads the property list from input, which is either an IO, StringIO, or a string. Format is an optional parameter - if false, the return value is the converted property list object. If true, the return value is a 2-element array, the first element being the returned value and the second being a symbol identifying the property list format.
35
+
36
+ ==== OSX::PropertyList.dump(output, obj, format = :xml1)
37
+
38
+ Dumps the property list object into output, which is either an IO or StringIO. Format determines the property list format to write out. The supported values are :xml1,, :binary1, and :openstep; however, OpenStep format appears to not be supported by the system for output anymore.
39
+
40
+ The valid formats are :xml1, :binary1, and :openstep. When loading a property list, if the format is something else (not possible under any current OS, but perhaps if a future OS includes another type) then the format will be :unknown.
41
+
42
+ This module also provides a method on Object:
43
+
44
+ ==== Object#to_plist(format = :xml1)
45
+
46
+ This is the same as PropertyList.dump except it outputs the property list as a string return value instead of writing it to a stream
47
+
48
+ This module also provides 2 methods on String:
49
+
50
+ ==== String#blob?
51
+
52
+ Returns whether the string is a blob.
53
+
54
+ ==== String#blob=
55
+
56
+ Sets whether the string is a blob.
57
+
58
+ A blob is a string that's been converted from a <data> property list item. When dumping to a property list, any strings that are blobs are written as <data> items rather than <string> items.
59
+
60
+ == LICENSE:
61
+
62
+ (The MIT License)
63
+
64
+ Copyright (c) 2008 Kevin Ballard
65
+
66
+ Permission is hereby granted, free of charge, to any person obtaining
67
+ a copy of this software and associated documentation files (the
68
+ 'Software'), to deal in the Software without restriction, including
69
+ without limitation the rights to use, copy, modify, merge, publish,
70
+ distribute, sublicense, and/or sell copies of the Software, and to
71
+ permit persons to whom the Software is furnished to do so, subject to
72
+ the following conditions:
73
+
74
+ The above copyright notice and this permission notice shall be
75
+ included in all copies or substantial portions of the Software.
76
+
77
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
78
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
79
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
80
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
81
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
82
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
83
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+
4
+ Hoe.new('osx-plist', "1.0") do |p|
5
+ p.author = "Kevin Ballard"
6
+ p.email = "kevin@sb.org"
7
+ p.summary = "Property List manipulation for OS X"
8
+ p.url = "http://www.github.com/kballard/plist"
9
+ p.spec_extras = {:extensions => "ext/plist/extconf.rb"}
10
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ require 'mkmf'
3
+ newFlags = " -arch ppc -arch i386 -isysroot /Developer/SDKs/MacOSX10.4u.sdk"
4
+ $CFLAGS += newFlags
5
+ $LDFLAGS += ' -framework CoreFoundation' + newFlags + ' -undefined suppress -flat_namespace'
6
+ $LIBRUBYARG_SHARED=""
7
+ create_makefile("osx/plist")
@@ -0,0 +1,579 @@
1
+ /*
2
+ * plist
3
+ * Kevin Ballard
4
+ *
5
+ * This is a Ruby extension to read/write Cocoa property lists
6
+ * Not surprisingly, it only works on OS X
7
+ *
8
+ * Copyright © 2005, Kevin Ballard
9
+ *
10
+ * Usage:
11
+ * This extension provides a module named OSX::PropertyList
12
+ * This module has two methods:
13
+ *
14
+ * PropertyList::load(obj, format = false)
15
+ * Takes either an IO stream open for reading or a String object
16
+ * Returns an object representing the property list
17
+ *
18
+ * Optionally takes a boolean format argument. If true, the
19
+ * return value is an array with the second value being
20
+ * the format of the plist, which can be one of
21
+ * :xml1, :binary1, or :openstep
22
+ *
23
+ * PropertyList::dump(io, obj, type = :xml1)
24
+ * Takes an IO stream (open for writing) and an object
25
+ * Writes the object to the IO stream as a property list
26
+ * Posible type values are :xml1 and :binary1
27
+ *
28
+ * It also adds a new method to Object:
29
+ *
30
+ * Object#to_plist(type = :xml1)
31
+ * Returns a string representation of the property list
32
+ * Possible type values are :xml1 and :binary1
33
+ *
34
+ * It also adds 2 new methods to String:
35
+ *
36
+ * String#blob=(b)
37
+ * Sets whether the string is a blob
38
+ *
39
+ * String#blob?
40
+ * Returns whether the string is a blob
41
+ *
42
+ * A blob string is turned into a CFData when dumped
43
+ *
44
+ */
45
+
46
+ /*
47
+ * Document-class: PropertyList
48
+ *
49
+ * The PropertyList module provides a means of converting a
50
+ * Ruby Object to a Property List.
51
+ *
52
+ * The various Objects that can be converted are the ones
53
+ * with an equivalent in CoreFoundation. This includes: String,
54
+ * Integer, Float, Boolean, Time, Hash, and Array.
55
+ *
56
+ * See also: String#blob?, String#blob=, and Object#to_plist
57
+ */
58
+
59
+ #include <ruby.h>
60
+ #include <st.h>
61
+ #include <CoreFoundation/CoreFoundation.h>
62
+
63
+ // Here's some convenience macros
64
+ #ifndef StringValue
65
+ #define StringValue(x) do { \
66
+ if (TYPE(x) != T_STRING) x = rb_str_to_str(x); \
67
+ } while (0)
68
+ #endif
69
+
70
+ static VALUE mOSX;
71
+ static VALUE mPlist;
72
+ static VALUE timeEpoch;
73
+ static VALUE ePropertyListError;
74
+
75
+ static VALUE id_gm;
76
+ static VALUE id_plus;
77
+ static VALUE id_minus;
78
+ static VALUE id_read;
79
+ static VALUE id_write;
80
+
81
+ static VALUE id_xml;
82
+ static VALUE id_binary;
83
+ static VALUE id_openstep;
84
+
85
+ static VALUE id_blob;
86
+
87
+ VALUE convertPropertyListRef(CFPropertyListRef plist);
88
+ VALUE convertStringRef(CFStringRef plist);
89
+ VALUE convertDictionaryRef(CFDictionaryRef plist);
90
+ VALUE convertArrayRef(CFArrayRef plist);
91
+ VALUE convertNumberRef(CFNumberRef plist);
92
+ VALUE convertBooleanRef(CFBooleanRef plist);
93
+ VALUE convertDataRef(CFDataRef plist);
94
+ VALUE convertDateRef(CFDateRef plist);
95
+ VALUE str_blob(VALUE self);
96
+ VALUE str_setBlob(VALUE self, VALUE b);
97
+
98
+ // Raises a Ruby exception with the given string
99
+ void raiseError(CFStringRef error) {
100
+ char *errBuffer = (char *)CFStringGetCStringPtr(error, kCFStringEncodingUTF8);
101
+ int freeBuffer = 0;
102
+ if (!errBuffer) {
103
+ int len = CFStringGetLength(error)*2+1;
104
+ errBuffer = ALLOC_N(char, len);
105
+ Boolean succ = CFStringGetCString(error, errBuffer, len, kCFStringEncodingUTF8);
106
+ if (!succ) {
107
+ CFStringGetCString(error, errBuffer, len, kCFStringEncodingMacRoman);
108
+ }
109
+ freeBuffer = 1;
110
+ }
111
+ rb_raise(ePropertyListError, (char *)errBuffer);
112
+ if (freeBuffer) free(errBuffer);
113
+ }
114
+
115
+ /* call-seq:
116
+ * PropertyList.load(obj) -> object
117
+ * PropertyList.load(obj, format) -> [object, format]
118
+ *
119
+ * Loads a property list from an IO stream or a String and creates
120
+ * an equivalent Object from it.
121
+ *
122
+ * If +format+ is provided, it returns one of
123
+ * <tt>:xml1</tt>, <tt>:binary1</tt>, or <tt>:openstep</tt>.
124
+ */
125
+ VALUE plist_load(int argc, VALUE *argv, VALUE self) {
126
+ VALUE io, retFormat;
127
+ int count = rb_scan_args(argc, argv, "11", &io, &retFormat);
128
+ if (count < 2) retFormat = Qfalse;
129
+ VALUE buffer;
130
+ if (RTEST(rb_respond_to(io, id_read))) {
131
+ // Read from IO
132
+ buffer = rb_funcall(io, id_read, 0);
133
+ } else {
134
+ StringValue(io);
135
+ buffer = io;
136
+ }
137
+ // For some reason, the CFReadStream version doesn't work with input < 6 characters
138
+ // but the CFDataRef version doesn't return format
139
+ // So lets use the CFDataRef version unless format is requested
140
+ CFStringRef error = NULL;
141
+ CFPropertyListRef plist;
142
+ CFPropertyListFormat format;
143
+ if (RTEST(retFormat)) {
144
+ // Format was requested
145
+ // now just in case, if the input is < 6 characters, we will pad it out with newlines
146
+ // we could do this in all cases, but I don't think it will work with binary
147
+ // even though binary shouldn't be < 6 characters
148
+ UInt8 *bytes;
149
+ int len;
150
+ if (RSTRING(buffer)->len < 6) {
151
+ bytes = ALLOC_N(UInt8, 6);
152
+ memset(bytes, '\n', 6);
153
+ MEMCPY(bytes, RSTRING(buffer)->ptr, UInt8, RSTRING(buffer)->len);
154
+ len = 6;
155
+ } else {
156
+ bytes = (UInt8 *)RSTRING(buffer)->ptr;
157
+ len = RSTRING(buffer)->len;
158
+ }
159
+ CFReadStreamRef readStream = CFReadStreamCreateWithBytesNoCopy(kCFAllocatorDefault, bytes, len, kCFAllocatorNull);
160
+ CFReadStreamOpen(readStream);
161
+ plist = CFPropertyListCreateFromStream(kCFAllocatorDefault, readStream, 0, kCFPropertyListImmutable, &format, &error);
162
+ CFReadStreamClose(readStream);
163
+ CFRelease(readStream);
164
+ } else {
165
+ // Format wasn't requested
166
+ CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8*)RSTRING(buffer)->ptr, RSTRING(buffer)->len, kCFAllocatorNull);
167
+ plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, kCFPropertyListImmutable, &error);
168
+ CFRelease(data);
169
+ }
170
+ if (error) {
171
+ raiseError(error);
172
+ CFRelease(error);
173
+ return Qnil;
174
+ }
175
+ VALUE obj = convertPropertyListRef(plist);
176
+ CFRelease(plist);
177
+ if (RTEST(retFormat)) {
178
+ VALUE ary = rb_ary_new();
179
+ rb_ary_push(ary, obj);
180
+ if (format == kCFPropertyListOpenStepFormat) {
181
+ retFormat = id_openstep;
182
+ } else if (format == kCFPropertyListXMLFormat_v1_0) {
183
+ retFormat = id_xml;
184
+ } else if (format == kCFPropertyListBinaryFormat_v1_0) {
185
+ retFormat = id_binary;
186
+ } else {
187
+ retFormat = rb_intern("unknown");
188
+ }
189
+ rb_ary_push(ary, ID2SYM(retFormat));
190
+ return ary;
191
+ } else {
192
+ return obj;
193
+ }
194
+ }
195
+
196
+ // Maps the property list object to a ruby object
197
+ VALUE convertPropertyListRef(CFPropertyListRef plist) {
198
+ CFTypeID typeID = CFGetTypeID(plist);
199
+ if (typeID == CFStringGetTypeID()) {
200
+ return convertStringRef((CFStringRef)plist);
201
+ } else if (typeID == CFDictionaryGetTypeID()) {
202
+ return convertDictionaryRef((CFDictionaryRef)plist);
203
+ } else if (typeID == CFArrayGetTypeID()) {
204
+ return convertArrayRef((CFArrayRef)plist);
205
+ } else if (typeID == CFNumberGetTypeID()) {
206
+ return convertNumberRef((CFNumberRef)plist);
207
+ } else if (typeID == CFBooleanGetTypeID()) {
208
+ return convertBooleanRef((CFBooleanRef)plist);
209
+ } else if (typeID == CFDataGetTypeID()) {
210
+ return convertDataRef((CFDataRef)plist);
211
+ } else if (typeID == CFDateGetTypeID()) {
212
+ return convertDateRef((CFDateRef)plist);
213
+ } else {
214
+ return Qnil;
215
+ }
216
+ }
217
+
218
+ // Converts a CFStringRef to a String
219
+ VALUE convertStringRef(CFStringRef plist) {
220
+ CFIndex byteCount;
221
+ CFRange range = CFRangeMake(0, CFStringGetLength(plist));
222
+ CFStringEncoding enc = kCFStringEncodingUTF8;
223
+ Boolean succ = CFStringGetBytes(plist, range, enc, 0, false, NULL, 0, &byteCount);
224
+ if (!succ) {
225
+ enc = kCFStringEncodingMacRoman;
226
+ CFStringGetBytes(plist, range, enc, 0, false, NULL, 0, &byteCount);
227
+ }
228
+ UInt8 *buffer = ALLOC_N(UInt8, byteCount);
229
+ CFStringGetBytes(plist, range, enc, 0, false, buffer, byteCount, NULL);
230
+ VALUE retval = rb_str_new((char *)buffer, (long)byteCount);
231
+ free(buffer);
232
+ return retval;
233
+ }
234
+
235
+ // Converts the keys and values of a CFDictionaryRef
236
+ void dictionaryConverter(const void *key, const void *value, void *context) {
237
+ rb_hash_aset((VALUE)context, convertPropertyListRef(key), convertPropertyListRef(value));
238
+ }
239
+
240
+ // Converts a CFDictionaryRef to a Hash
241
+ VALUE convertDictionaryRef(CFDictionaryRef plist) {
242
+ VALUE hash = rb_hash_new();
243
+ CFDictionaryApplyFunction(plist, dictionaryConverter, (void *)hash);
244
+ return hash;
245
+ }
246
+
247
+ // Converts the values of a CFArrayRef
248
+ void arrayConverter(const void *value, void *context) {
249
+ rb_ary_push((VALUE)context, convertPropertyListRef(value));
250
+ }
251
+
252
+ // Converts a CFArrayRef to an Array
253
+ VALUE convertArrayRef(CFArrayRef plist) {
254
+ VALUE array = rb_ary_new();
255
+ CFRange range = CFRangeMake(0, CFArrayGetCount(plist));
256
+ CFArrayApplyFunction(plist, range, arrayConverter, (void *)array);
257
+ return array;
258
+ }
259
+
260
+ // Converts a CFNumberRef to a Number
261
+ VALUE convertNumberRef(CFNumberRef plist) {
262
+ if (CFNumberIsFloatType(plist)) {
263
+ double val;
264
+ CFNumberGetValue(plist, kCFNumberDoubleType, &val);
265
+ return rb_float_new(val);
266
+ } else {
267
+ #ifdef LL2NUM
268
+ long long val;
269
+ CFNumberGetValue(plist, kCFNumberLongLongType, &val);
270
+ return LL2NUM(val);
271
+ #else
272
+ long val;
273
+ CFNumberGetValue(plist, kCFNumberLongType, &val);
274
+ return LONG2NUM(val);
275
+ #endif
276
+ }
277
+ }
278
+
279
+ // Converts a CFBooleanRef to a Boolean
280
+ VALUE convertBooleanRef(CFBooleanRef plist) {
281
+ if (CFBooleanGetValue(plist)) {
282
+ return Qtrue;
283
+ } else {
284
+ return Qfalse;
285
+ }
286
+ }
287
+
288
+ // Converts a CFDataRef to a String (with blob set to true)
289
+ VALUE convertDataRef(CFDataRef plist) {
290
+ const UInt8 *bytes = CFDataGetBytePtr(plist);
291
+ CFIndex len = CFDataGetLength(plist);
292
+ VALUE str = rb_str_new((char *)bytes, (long)len);
293
+ str_setBlob(str, Qtrue);
294
+ return str;
295
+ }
296
+
297
+ // Converts a CFDateRef to a Time
298
+ VALUE convertDateRef(CFDateRef plist) {
299
+ CFAbsoluteTime seconds = CFDateGetAbsoluteTime(plist);
300
+
301
+ // trunace the time since Ruby's Time object stores it as a 32 bit signed offset from 1970 (undocumented)
302
+ const float min_time = -3124310400.0f;
303
+ const float max_time = 1169098047.0f;
304
+ seconds = seconds < min_time ? min_time : (seconds > max_time ? max_time : seconds);
305
+
306
+ return rb_funcall(timeEpoch, id_plus, 1, rb_float_new(seconds));
307
+ }
308
+
309
+ CFPropertyListRef convertObject(VALUE obj);
310
+
311
+ // Converts a PropertyList object to a string representation
312
+ VALUE convertPlistToString(CFPropertyListRef plist, CFPropertyListFormat format) {
313
+ CFWriteStreamRef writeStream = CFWriteStreamCreateWithAllocatedBuffers(kCFAllocatorDefault, kCFAllocatorDefault);
314
+ CFWriteStreamOpen(writeStream);
315
+ CFStringRef error = NULL;
316
+ CFPropertyListWriteToStream(plist, writeStream, format, &error);
317
+ CFWriteStreamClose(writeStream);
318
+ if (error) {
319
+ raiseError(error);
320
+ return Qnil;
321
+ }
322
+ CFDataRef data = CFWriteStreamCopyProperty(writeStream, kCFStreamPropertyDataWritten);
323
+ CFRelease(writeStream);
324
+ VALUE plistData = convertDataRef(data);
325
+ CFRelease(data);
326
+ return plistData;
327
+ }
328
+
329
+ /* call-seq:
330
+ * PropertyList.dump(io, obj) -> Integer
331
+ * PropertyList.dump(io, obj, format) -> Integer
332
+ *
333
+ * Writes the property list representation of +obj+
334
+ * to the IO stream (must be open for writing).
335
+ *
336
+ * +format+ can be one of <tt>:xml1</tt> or <tt>:binary1</tt>.
337
+ *
338
+ * Returns the number of bytes written, or +nil+ if
339
+ * the object could not be represented as a property list
340
+ */
341
+ VALUE plist_dump(int argc, VALUE *argv, VALUE self) {
342
+ VALUE io, obj, type;
343
+ int count = rb_scan_args(argc, argv, "21", &io, &obj, &type);
344
+ if (count < 3) {
345
+ type = id_xml;
346
+ } else {
347
+ type = rb_to_id(type);
348
+ }
349
+ if (type != id_xml && type != id_binary && type != id_openstep) {
350
+ rb_raise(rb_eArgError, "Argument 3 must be one of :xml1, :binary1, or :openstep");
351
+ return Qnil;
352
+ }
353
+ if (!RTEST(rb_respond_to(io, id_write))) {
354
+ rb_raise(rb_eArgError, "Argument 1 must be an IO object");
355
+ return Qnil;
356
+ }
357
+ CFPropertyListRef plist = convertObject(obj);
358
+ CFPropertyListFormat format;
359
+ if (type == id_xml) {
360
+ format = kCFPropertyListXMLFormat_v1_0;
361
+ } else if (type == id_binary) {
362
+ format = kCFPropertyListBinaryFormat_v1_0;
363
+ } else if (type == id_openstep) {
364
+ format = kCFPropertyListOpenStepFormat;
365
+ }
366
+ VALUE data = convertPlistToString(plist, format);
367
+ if (NIL_P(data)) {
368
+ return Qnil;
369
+ } else {
370
+ return rb_funcall(io, id_write, 1, data);
371
+ }
372
+ }
373
+
374
+ /* call-seq:
375
+ * object.to_plist -> String
376
+ * object.to_plist(format) -> String
377
+ *
378
+ * Converts the object to a property list representation
379
+ * and returns it as a string.
380
+ *
381
+ * +format+ can be one of <tt>:xml1</tt> or <tt>:binary1</tt>.
382
+ */
383
+ VALUE obj_to_plist(int argc, VALUE *argv, VALUE self) {
384
+ VALUE type;
385
+ int count = rb_scan_args(argc, argv, "01", &type);
386
+ if (count < 1) {
387
+ type = id_xml;
388
+ } else {
389
+ type = rb_to_id(type);
390
+ }
391
+ if (type != id_xml && type != id_binary && type != id_openstep) {
392
+ rb_raise(rb_eArgError, "Argument 2 must be one of :xml1, :binary1, or :openstep");
393
+ return Qnil;
394
+ }
395
+ CFPropertyListRef plist = convertObject(self);
396
+ CFPropertyListFormat format;
397
+ if (type == id_xml) {
398
+ format = kCFPropertyListXMLFormat_v1_0;
399
+ } else if (type == id_binary) {
400
+ format = kCFPropertyListBinaryFormat_v1_0;
401
+ } else if (type == id_openstep) {
402
+ format = kCFPropertyListOpenStepFormat;
403
+ }
404
+ VALUE data = convertPlistToString(plist, format);
405
+ CFRelease(plist);
406
+ if (type == id_xml || type == id_binary) {
407
+ str_setBlob(data, Qfalse);
408
+ }
409
+ return data;
410
+ }
411
+
412
+ CFPropertyListRef convertString(VALUE obj);
413
+ CFDictionaryRef convertHash(VALUE obj);
414
+ CFArrayRef convertArray(VALUE obj);
415
+ CFNumberRef convertNumber(VALUE obj);
416
+ CFDateRef convertTime(VALUE obj);
417
+
418
+ // Converts an Object to a CFTypeRef
419
+ CFPropertyListRef convertObject(VALUE obj) {
420
+ switch (TYPE(obj)) {
421
+ case T_STRING: return convertString(obj); break;
422
+ case T_HASH: return convertHash(obj); break;
423
+ case T_ARRAY: return convertArray(obj); break;
424
+ case T_FLOAT:
425
+ case T_FIXNUM:
426
+ case T_BIGNUM: return convertNumber(obj); break;
427
+ case T_TRUE: return kCFBooleanTrue; break;
428
+ case T_FALSE: return kCFBooleanFalse; break;
429
+ default: if (rb_obj_is_kind_of(obj, rb_cTime)) return convertTime(obj);
430
+ }
431
+ rb_raise(rb_eArgError, "An object in the argument tree could not be converted");
432
+ return NULL;
433
+ }
434
+
435
+ // Converts a String to a CFStringRef
436
+ CFPropertyListRef convertString(VALUE obj) {
437
+ if (RTEST(str_blob(obj))) {
438
+ // convert to CFDataRef
439
+ StringValue(obj);
440
+ CFDataRef data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)RSTRING(obj)->ptr, (CFIndex)RSTRING(obj)->len);
441
+ return data;
442
+ } else {
443
+ // convert to CFStringRef
444
+ StringValue(obj);
445
+ CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)RSTRING(obj)->ptr, (CFIndex)RSTRING(obj)->len, kCFStringEncodingUTF8, false);
446
+ if (!string) {
447
+ // try MacRoman
448
+ string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)RSTRING(obj)->ptr, (CFIndex)RSTRING(obj)->len, kCFStringEncodingMacRoman, false);
449
+ }
450
+ return string;
451
+ }
452
+ }
453
+
454
+ // Converts the keys and values of a Hash to CFTypeRefs
455
+ int iterateHash(VALUE key, VALUE val, VALUE dict) {
456
+ CFPropertyListRef dKey = convertObject(key);
457
+ CFPropertyListRef dVal = convertObject(val);
458
+ CFDictionaryAddValue((CFMutableDictionaryRef)dict, dKey, dVal);
459
+ CFRelease(dKey);
460
+ CFRelease(dVal);
461
+ return ST_CONTINUE;
462
+ }
463
+
464
+ // Converts a Hash to a CFDictionaryREf
465
+ CFDictionaryRef convertHash(VALUE obj) {
466
+ CFIndex count = (CFIndex)RHASH(obj)->tbl->num_entries;
467
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
468
+ st_foreach(RHASH(obj)->tbl, iterateHash, (VALUE)dict);
469
+ return dict;
470
+ }
471
+
472
+ // Converts an Array to a CFArrayRef
473
+ CFArrayRef convertArray(VALUE obj) {
474
+ CFIndex count = (CFIndex)RARRAY(obj)->len;
475
+ CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, count, &kCFTypeArrayCallBacks);
476
+ int i;
477
+ for (i = 0; i < count; i++) {
478
+ CFPropertyListRef aVal = convertObject(RARRAY(obj)->ptr[i]);
479
+ CFArrayAppendValue(array, aVal);
480
+ CFRelease(aVal);
481
+ }
482
+ return array;
483
+ }
484
+
485
+ // Converts a Number to a CFNumberRef
486
+ CFNumberRef convertNumber(VALUE obj) {
487
+ void *valuePtr;
488
+ CFNumberType type;
489
+ switch (TYPE(obj)) {
490
+ case T_FLOAT: {
491
+ double num = NUM2DBL(obj);
492
+ valuePtr = &num;
493
+ type = kCFNumberDoubleType;
494
+ break;
495
+ }
496
+ case T_FIXNUM: {
497
+ int num = NUM2INT(obj);
498
+ valuePtr = &num;
499
+ type = kCFNumberIntType;
500
+ break;
501
+ }
502
+ case T_BIGNUM: {
503
+ #ifdef NUM2LL
504
+ long long num = NUM2LL(obj);
505
+ type = kCFNumberLongLongType;
506
+ #else
507
+ long num = NUM2LONG(obj);
508
+ type = kCFNumberLongType;
509
+ #endif
510
+ valuePtr = &num;
511
+ break;
512
+ }
513
+ default:
514
+ rb_raise(rb_eStandardError, "ERROR: Wrong object type passed to convertNumber");
515
+ return NULL;
516
+ }
517
+ CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, type, valuePtr);
518
+ return number;
519
+ }
520
+
521
+ // Converts a Time to a CFDateRef
522
+ CFDateRef convertTime(VALUE obj) {
523
+ VALUE secs = rb_funcall(obj, id_minus, 1, timeEpoch);
524
+ CFDateRef date = CFDateCreate(kCFAllocatorDefault, NUM2DBL(secs));
525
+ return date;
526
+ }
527
+
528
+ /* call-seq:
529
+ * str.blob? -> Boolean
530
+ *
531
+ * Returns whether or not +str+ is a blob.
532
+ */
533
+ VALUE str_blob(VALUE self) {
534
+ VALUE blob = rb_attr_get(self, id_blob);
535
+ if (NIL_P(blob)) {
536
+ return Qfalse;
537
+ } else {
538
+ return blob;
539
+ }
540
+ }
541
+
542
+ /* call-seq:
543
+ * str.blob = bool -> bool
544
+ *
545
+ * Sets the blob status of +str+.
546
+ */
547
+ VALUE str_setBlob(VALUE self, VALUE b) {
548
+ if (TYPE(b) == T_TRUE || TYPE(b) == T_FALSE) {
549
+ return rb_ivar_set(self, id_blob, b);
550
+ } else {
551
+ rb_raise(rb_eArgError, "Argument 1 must be true or false");
552
+ return Qnil;
553
+ }
554
+ }
555
+
556
+ /* Bridge to CoreFoundation for reading/writing Property Lists.
557
+ * Only works when CoreFoundation is available.
558
+ */
559
+ void Init_plist() {
560
+ mOSX = rb_define_module("OSX");
561
+ mPlist = rb_define_module_under(mOSX, "PropertyList");
562
+ rb_define_module_function(mPlist, "load", plist_load, -1);
563
+ rb_define_module_function(mPlist, "dump", plist_dump, -1);
564
+ rb_define_method(rb_cObject, "to_plist", obj_to_plist, -1);
565
+ rb_define_method(rb_cString, "blob?", str_blob, 0);
566
+ rb_define_method(rb_cString, "blob=", str_setBlob, 1);
567
+ ePropertyListError = rb_define_class_under(mOSX, "PropertyListError", rb_eStandardError);
568
+ id_gm = rb_intern("gm");
569
+ timeEpoch = rb_funcall(rb_cTime, id_gm, 1, INT2FIX(2001));
570
+ rb_define_const(mPlist, "EPOCH", timeEpoch);
571
+ id_plus = rb_intern("+");
572
+ id_minus = rb_intern("-");
573
+ id_read = rb_intern("read");
574
+ id_write = rb_intern("write");
575
+ id_xml = rb_intern("xml1");
576
+ id_binary = rb_intern("binary1");
577
+ id_openstep = rb_intern("openstep");
578
+ id_blob = rb_intern("@blob");
579
+ }
@@ -0,0 +1,25 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{osx-plist}
3
+ s.version = "1.0"
4
+
5
+ s.specification_version = 2 if s.respond_to? :specification_version=
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Kevin Ballard"]
9
+ s.date = %q{2008-04-25}
10
+ s.description = %q{osx/plist is a Ruby library for manipulating Property Lists natively using the built-in support in OS X.}
11
+ s.email = %q{kevin@sb.org}
12
+ s.extensions = ["ext/plist/extconf.rb"]
13
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
14
+ s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "ext/plist/extconf.rb", "ext/plist/plist.c", "plist.gemspec", "test/fixtures/xml_plist", "test/suite.rb", "test/test_plist.rb"]
15
+ s.has_rdoc = true
16
+ s.homepage = %q{http://www.github.com/kballard/plist}
17
+ s.rdoc_options = ["--main", "README.txt"]
18
+ s.require_paths = ["ext"]
19
+ s.rubyforge_project = %q{osx-plist}
20
+ s.rubygems_version = %q{1.0.1}
21
+ s.summary = %q{Property List manipulation for OS X}
22
+ s.test_files = ["test/test_plist.rb"]
23
+
24
+ s.add_dependency(%q<hoe>, [">= 1.5.1"])
25
+ end
@@ -0,0 +1,27 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/OSX::PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>string!</key>
6
+ <string>indeedy</string>
7
+ <key>bar</key>
8
+ <array>
9
+ <integer>1</integer>
10
+ <integer>2</integer>
11
+ <integer>3</integer>
12
+ </array>
13
+ <key>foo</key>
14
+ <dict>
15
+ <key>correct?</key>
16
+ <true/>
17
+ <key>pi</key>
18
+ <real>3.14159265</real>
19
+ <key>random</key>
20
+ <data>
21
+ I0VniQ==
22
+ </data>
23
+ <key>today</key>
24
+ <date>2005-04-28T06:32:56Z</date>
25
+ </dict>
26
+ </dict>
27
+ </plist>
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+
3
+ tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"]
4
+ tests.each do |file|
5
+ require file
6
+ end
@@ -0,0 +1,69 @@
1
+ require 'rubygems'
2
+ require 'osx/plist'
3
+ require 'stringio'
4
+ require 'test/unit'
5
+
6
+ class TestPlist < Test::Unit::TestCase
7
+ def test_string
8
+ plist = OSX::PropertyList.load("{foo = bar; }")
9
+ assert_equal( { "foo" => "bar" }, plist )
10
+
11
+ plist, format = OSX::PropertyList.load("{foo = bar; }", true)
12
+ assert_equal( { "foo" => "bar" }, plist )
13
+ assert_equal( :openstep, format )
14
+
15
+ # make sure sources < 6 characters work
16
+ plist = OSX::PropertyList.load("foo")
17
+ assert_equal( "foo", plist )
18
+
19
+ # make sure it works with format too
20
+ plist, format = OSX::PropertyList.load("foo", true)
21
+ assert_equal( "foo", plist )
22
+ assert_equal( :openstep, format )
23
+
24
+ assert_raise(OSX::PropertyListError) { OSX::PropertyList.load("") }
25
+ end
26
+
27
+ def setup_hash
28
+ time = Time.gm(2005, 4, 28, 6, 32, 56)
29
+ random = "\x23\x45\x67\x89"
30
+ random.blob = true
31
+ {
32
+ "string!" => "indeedy",
33
+ "bar" => [ 1, 2, 3 ],
34
+ "foo" => {
35
+ "correct?" => true,
36
+ "pi" => 3.14159265,
37
+ "random" => random,
38
+ "today" => time,
39
+ }
40
+ }
41
+ end
42
+
43
+ def test_io
44
+ plist, format = OSX::PropertyList.load(File.read("#{File.dirname(__FILE__)}/fixtures/xml_plist"), true)
45
+
46
+ hash = setup_hash
47
+
48
+ assert_equal(hash, plist)
49
+ assert_equal(true, plist['foo']['random'].blob?)
50
+ assert_equal(false, plist['string!'].blob?)
51
+
52
+ assert_equal(:xml1, format)
53
+ end
54
+
55
+ def test_dump
56
+ str = StringIO.new("", "w")
57
+ hash = setup_hash
58
+ OSX::PropertyList.dump(str, hash)
59
+ hash2 = OSX::PropertyList.load(str.string)
60
+ assert_equal(hash, hash2)
61
+ end
62
+
63
+ def test_to_plist
64
+ assert_raise(OSX::PropertyListError) { "foo".to_plist(:openstep) }
65
+ assert_equal("foo", OSX::PropertyList.load("foo".to_plist))
66
+ hash = setup_hash()
67
+ assert_equal(hash, OSX::PropertyList.load(hash.to_plist))
68
+ end
69
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kballard-osx-plist
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.0"
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Ballard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-04-25 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.5.1
23
+ version:
24
+ description: osx/plist is a Ruby library for manipulating Property Lists natively using the built-in support in OS X.
25
+ email: kevin@sb.org
26
+ executables: []
27
+
28
+ extensions:
29
+ - ext/plist/extconf.rb
30
+ extra_rdoc_files:
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ files:
35
+ - History.txt
36
+ - Manifest.txt
37
+ - README.txt
38
+ - Rakefile
39
+ - ext/plist/extconf.rb
40
+ - ext/plist/plist.c
41
+ - plist.gemspec
42
+ - test/fixtures/xml_plist
43
+ - test/suite.rb
44
+ - test/test_plist.rb
45
+ has_rdoc: true
46
+ homepage: http://www.github.com/kballard/plist
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --main
50
+ - README.txt
51
+ require_paths:
52
+ - ext
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project: osx-plist
68
+ rubygems_version: 1.0.1
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: Property List manipulation for OS X
72
+ test_files:
73
+ - test/test_plist.rb