osx-plist 1.0.3

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.
@@ -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