osx-plist 1.0.3

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