plist4r 0.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +11 -0
  3. data/LICENSE +3 -1
  4. data/README.rdoc +25 -122
  5. data/Rakefile +14 -0
  6. data/VERSION +1 -1
  7. data/bin/plist4r +2 -0
  8. data/ext/osx_plist/Makefile +157 -0
  9. data/ext/osx_plist/extconf.rb +9 -0
  10. data/ext/osx_plist/plist.c +606 -0
  11. data/ext/osx_plist/plist.o +0 -0
  12. data/lib/plist4r.rb +6 -3
  13. data/lib/plist4r/application.rb +1 -2
  14. data/lib/plist4r/backend.rb +102 -34
  15. data/lib/plist4r/backend/c_f_property_list.rb +65 -0
  16. data/lib/plist4r/backend/c_f_property_list/LICENSE +19 -0
  17. data/lib/plist4r/backend/c_f_property_list/README +34 -0
  18. data/lib/plist4r/backend/c_f_property_list/cfpropertylist.rb +6 -0
  19. data/lib/plist4r/backend/c_f_property_list/rbBinaryCFPropertyList.rb +663 -0
  20. data/lib/plist4r/backend/c_f_property_list/rbCFPlistError.rb +26 -0
  21. data/lib/plist4r/backend/c_f_property_list/rbCFPropertyList.rb +348 -0
  22. data/lib/plist4r/backend/c_f_property_list/rbCFTypes.rb +241 -0
  23. data/lib/plist4r/backend/c_f_property_list/rbXMLCFPropertyList.rb +116 -0
  24. data/lib/plist4r/backend/example.rb +37 -52
  25. data/lib/plist4r/backend/haml.rb +47 -36
  26. data/lib/plist4r/backend/libxml4r.rb +24 -20
  27. data/lib/plist4r/backend/osx_plist.rb +82 -0
  28. data/lib/plist4r/backend/ruby_cocoa.rb +172 -54
  29. data/lib/plist4r/backend/test/data_types.rb +163 -0
  30. data/lib/plist4r/backend/test/harness.rb +255 -0
  31. data/lib/plist4r/backend/test/output.rb +47 -0
  32. data/lib/plist4r/backend_base.rb +4 -2
  33. data/lib/plist4r/{options.rb → cli.rb} +2 -1
  34. data/lib/plist4r/commands.rb +13 -8
  35. data/lib/plist4r/config.rb +36 -9
  36. data/lib/plist4r/docs/Backends.html +59 -0
  37. data/lib/plist4r/docs/DeveloperGuide.rdoc +53 -0
  38. data/lib/plist4r/docs/EditingPlistFiles.rdoc +88 -0
  39. data/lib/plist4r/docs/InfoPlistExample.rdoc +33 -0
  40. data/lib/plist4r/docs/LaunchdPlistExample.rdoc +33 -0
  41. data/lib/plist4r/docs/PlistKeyNames.rdoc +47 -0
  42. data/lib/plist4r/mixin/array_dict.rb +61 -0
  43. data/lib/plist4r/mixin/data_methods.rb +178 -54
  44. data/lib/plist4r/mixin/haml4r.rb +4 -0
  45. data/lib/plist4r/mixin/haml4r/css_attributes.rb +19 -0
  46. data/lib/plist4r/mixin/haml4r/examples.rb +261 -0
  47. data/lib/plist4r/mixin/haml4r/haml_table_example.rb +79 -0
  48. data/lib/plist4r/mixin/haml4r/table.rb +157 -0
  49. data/lib/plist4r/mixin/haml4r/table_cell.rb +160 -0
  50. data/lib/plist4r/mixin/haml4r/table_cells.rb +485 -0
  51. data/lib/plist4r/mixin/haml4r/table_section.rb +101 -0
  52. data/lib/plist4r/mixin/ordered_hash.rb +9 -1
  53. data/lib/plist4r/mixin/popen4.rb +1 -1
  54. data/lib/plist4r/mixin/ruby_stdlib.rb +154 -1
  55. data/lib/plist4r/mixin/script.rb +133 -0
  56. data/lib/plist4r/mixin/table.rb +435 -0
  57. data/lib/plist4r/plist.rb +272 -94
  58. data/lib/plist4r/plist_cache.rb +42 -43
  59. data/lib/plist4r/plist_type.rb +31 -74
  60. data/lib/plist4r/plist_type/info.rb +157 -3
  61. data/lib/plist4r/plist_type/launchd.rb +54 -48
  62. data/lib/plist4r/plist_type/plist.rb +1 -3
  63. data/plist4r.gemspec +74 -14
  64. data/spec/{examples.rb → launchd_examples.rb} +131 -139
  65. data/spec/plist4r/application_spec.rb +37 -0
  66. data/spec/plist4r/backend_spec.rb +256 -0
  67. data/spec/plist4r/cli_spec.rb +25 -0
  68. data/spec/plist4r/commands_spec.rb +20 -0
  69. data/spec/plist4r/config_spec.rb +38 -0
  70. data/spec/plist4r/mixin/array_dict_spec.rb +120 -0
  71. data/spec/plist4r/mixin/data_methods_spec.rb +96 -0
  72. data/spec/plist4r/mixin/haml4r/examples.rb +261 -0
  73. data/spec/plist4r/mixin/ruby_stdlib_spec.rb +228 -0
  74. data/spec/plist4r/plist_cache_spec.rb +261 -0
  75. data/spec/plist4r/plist_spec.rb +841 -23
  76. data/spec/plist4r/plist_type_spec.rb +126 -0
  77. data/spec/plist4r_spec.rb +53 -27
  78. data/spec/scratchpad.rb +226 -0
  79. data/spec/spec_helper.rb +5 -1
  80. metadata +109 -23
  81. data/lib/plist4r/backend/plutil.rb +0 -25
  82. data/lib/plist4r/mixin.rb +0 -7
  83. data/plists/array_mini.xml +0 -14
  84. data/plists/example_big_binary.plist +0 -0
  85. data/plists/example_medium_binary_launchd.plist +0 -0
  86. data/plists/example_medium_launchd.xml +0 -53
  87. data/plists/mini.xml +0 -12
  88. data/test.rb +0 -40
data/.gitignore CHANGED
@@ -21,3 +21,7 @@ pkg
21
21
  ## PROJECT::SPECIFIC
22
22
  .yardoc
23
23
  doc
24
+
25
+ # Ruby
26
+ *.bundle
27
+
data/.yardopts CHANGED
@@ -1 +1,12 @@
1
1
  --no-private
2
+ --exclude "backend\/c_f_property_list\/cf*"
3
+ --exclude "backend\/c_f_property_list\/rb*"
4
+ --exclude "mixin\/haml4r*"
5
+ -
6
+ "lib/plist4r/docs/InfoPlistExample.rdoc"
7
+ "lib/plist4r/docs/LaunchdPlistExample.rdoc"
8
+ "lib/plist4r/docs/EditingPlistFiles.rdoc"
9
+ "lib/plist4r/docs/PlistKeyNames.rdoc"
10
+ "lib/plist4r/docs/Backends.html"
11
+ "lib/plist4r/docs/DeveloperGuide.rdoc"
12
+ "LICENSE"
data/LICENSE CHANGED
@@ -1,4 +1,6 @@
1
- Copyright (c) 2009 dreamcat4
1
+ = MIT License
2
+
3
+ Copyright (c) 2009 Dreamcat4
2
4
 
3
5
  Permission is hereby granted, free of charge, to any person obtaining
4
6
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -1,10 +1,8 @@
1
1
  = plist4r
2
2
 
3
- Plist4r is a friendly ruby library for reading and writing plist files.
3
+ Plist4r is a friendly rubygem / ruby lib for handling plist files.
4
4
 
5
- ==== Current status: Beta, 0.2.x series
6
-
7
- We can read / write various kinds of plist files reliably. The API interfaces (for the pluggable backends and plist_types) are mature. The user API works well. Searchable Documentation is included, complete with examples.
5
+ * Documentation at http://dreamcat4.github.com/plist4r
8
6
 
9
7
  == Installation
10
8
 
@@ -14,7 +12,7 @@ We can read / write various kinds of plist files reliably. The API interfaces (f
14
12
 
15
13
  require 'plist4r'
16
14
 
17
- Plist4r::Config.default_dir "/Library/LaunchDaemons"
15
+ Plist4r::Config.default_path = "/Library/LaunchDaemons"
18
16
  filename = "com.github.myservice.plist"
19
17
  p = Plist4r.open(filename)
20
18
 
@@ -24,130 +22,35 @@ We can read / write various kinds of plist files reliably. The API interfaces (f
24
22
  p.file_format
25
23
  # => :xml
26
24
 
27
- p.<< do
28
- ProgramArguments ["/usr/local/bin/myservice"]
29
- end
30
-
31
25
  p.edit do
32
- WatchPaths ["/var/db/myservice"]
26
+ program_arguments ["/usr/local/bin/myservice"]
27
+ watch_paths ["/var/db/myservice"]
33
28
  end
34
29
 
35
30
  p.save
36
31
 
37
- == Plist4r Backends
38
-
39
- There are now a number of ruby libraries which can read / write plist files. The aim of plist4r is to utilize the individual best features from all of those libraries, as a series of "backends". And hide those behind a "frontend" that is easy to work with.
40
-
41
- Backends often only need to be a single ruby file, which implements the Plist4r API methods and calls out to other (existing) ruby code. No single backend has to provide the whole API. Instead, Plist4r simply iterates over all of the backends it knows about, and then calls the first backend that can responds to the API method.
42
-
43
- There are just 6 supported API methods
44
-
45
- ApiMethods = %w[from_string to_xml to_binary to_next_step open save]
46
-
47
- And (as above) the 3 supported Plist file formats are
48
-
49
- FileFormats = %w[binary xml next_step]
50
-
51
- We believe thats allright for most uses, and decided to include `next_step` for completeness. `NextStep` is also known by other names such as `OpenStep` and (more updated version) `GNU Step`. For example the apple `defaults` command on Mac OS-X will still return `NextStep` formatted plist data.
52
-
53
- == Plist4r Types
54
-
55
- A Plist type can be one of `%w[plist info launchd]`, and is the data type for the whole plist file. The plist data type provides convenience methods for setting the Type-specific data structures. For example "Sockets" in a launchd plist.
56
-
57
- Plist types are also useful to disallow keys which arent recognized or supported by that format. Setting `:strict_keys true` the Plist4r::Config object will globaly enable strict keys.
58
-
59
- ::Plist4r::Config[:unsupported_keys] = true
60
-
61
- Or individually, per plist object with
62
-
63
- plist.strict_keys false
64
-
65
- Default is false, which allows editing of any arbitrary plist keys. We think thats a good choice, since unsupported keys can already be present in many existing plist files.
66
-
67
- == More Examples
68
-
69
- module ::Plist4r::Backend::MyPlistReaderWriter
70
- # implement some plist4r api calls here
71
- end
72
-
73
- # append my backend to the end of the list
74
- Plist4r::Config[:backends] << :my_plist_reader_writer
75
-
76
- # or to the front of the list (executes first)
77
- Plist4r::Config[:backends].insert 0 :my_plist_reader_writer
78
-
79
- # The default directory to load / save files from
80
- Plist4r::Config.default_path "/Library/Cars"
81
-
82
- car = Plist4r.new("car.plist")
83
-
84
- car.load
85
-
86
- car.file_format :binary
87
- # car.plist_type :car # not implemented *yet*
88
-
89
- car.save
90
-
91
- car.<< do
92
- road_legal true
93
- brake_light_color "red"
94
- end
95
-
96
- car.save_as("car2.plist", :binary => true)
97
-
98
- car.<< do
99
- eyes "blue"
100
- end
101
- # => Exception, invalid plist key name "Eyes"
102
-
103
- car.<< do
104
- tyres "Pirelli"
105
- end
106
-
107
- car.to_xml
108
- # => xml string
109
-
110
- == Remaining Work
111
-
112
- Plist4r has now moved from alpha to beta - quality software. TBC...
113
-
114
- * Regression Tests (rspec)
115
- * Test harness for the backends
116
- * Testing of the individual backends
117
- * A Plist Type for Info.plist
118
- * Tests for Plist Types
119
-
120
- == Notes on Patches/Pull Requests
121
-
122
- * Fork the project, and create a topic branch as per {these instructions}[http://wiki.opscode.com/display/opscode/Working+with+Git].
123
- * Make your feature addition or bug fix.
124
- * Include documentation for it.
125
- * Include a regression test for it. So I dont break it in a future version unintentionally.
126
-
127
- == Contributors
128
-
129
- Popen4
130
- * Ara T Howard
131
-
132
- ActiveSupport::OrderedHash
133
- * Copyright (c) 2005 David Hansson,
134
- * Copyright (c) 2007 Mauricio Fernandez, Sam Stephenson
135
- * Copyright (c) 2008 Steve Purcell, Josh Peek
136
- * Copyright (c) 2009 Christoffer Sawicki
137
-
138
- Mixlib::Config
139
- * Author:: Adam Jacob
140
- * Author:: Nuo Yan
141
- * Author:: Christopher Brown
142
- * Copyright:: Copyright (c) 2008 Opscode, Inc.
143
-
144
- Backends...
145
-
146
- Haml, Libxml4r, RubyCocoa
147
- * Dreamcat4
32
+ == Overview
33
+
34
+ * For convenience we invoke {Plist4r.open} and {Plist4r.new} to initially create a plist object.
35
+
36
+ * And use {Plist4r::Config} to set any global configuration defaults.
37
+
38
+ * See {file:Backends} for the backends performance data.
39
+
40
+ Examples
41
+ * See {file:InfoPlistExample}
42
+ * See {file:LaunchdPlistExample}
43
+
44
+ Recommended reading
45
+ * See {file:EditingPlistFiles}
46
+ * See {file:PlistKeyNames}
47
+ * Please see the inline source-code documentation on {Plist4r::Plist}
48
+
49
+ How to contribute
50
+ * {file:DeveloperGuide}
148
51
 
149
52
  == Copyright
150
53
 
151
- Copyright (c) 2010 Dreamcat4. See LICENSE for details.
54
+ Plist4r is Copyright (c) 2009 under MIT License. See {file:LICENSE} for details.
152
55
 
153
56
 
data/Rakefile CHANGED
@@ -10,9 +10,13 @@ begin
10
10
  gem.email = "dreamcat4@gmail.com"
11
11
  gem.homepage = "http://github.com/dreamcat4/plist4r"
12
12
  gem.authors = ["dreamcat4"]
13
+ gem.add_dependency "libxml-ruby"
14
+ gem.add_dependency "haml"
15
+ gem.add_dependency "libxml4r"
13
16
  gem.add_development_dependency "rspec", ">= 1.2.9"
14
17
  gem.add_development_dependency "yard", ">= 0"
15
18
  gem.add_development_dependency "cucumber", ">= 0"
19
+ gem.files.include %w(lib/plist4r/cli.rb) # no idea why this file gets ommited
16
20
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
21
  end
18
22
  Jeweler::GemcutterTasks.new
@@ -47,6 +51,16 @@ end
47
51
 
48
52
  task :default => :spec
49
53
 
54
+ namespace :backend do
55
+ task :tests do
56
+ require 'lib/plist4r'
57
+ require 'plist4r/backend/test/output'
58
+ o = Plist4r::Backend::Test::Output.new
59
+ puts o
60
+ o.write_html_file
61
+ end
62
+ end
63
+
50
64
  require 'yard'
51
65
  YARD::Rake::YardocTask.new do |t|
52
66
  t.after = lambda { `touch doc/.nojekyll` }
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 1.0.0
data/bin/plist4r CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'rubygems'
4
+
3
5
  dir = File.expand_path "../lib", File.dirname(__FILE__)
4
6
  $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
5
7
 
@@ -0,0 +1,157 @@
1
+
2
+ SHELL = /bin/sh
3
+
4
+ #### Start of system configuration section. ####
5
+
6
+ srcdir = .
7
+ topdir = /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin10.0
8
+ hdrdir = $(topdir)
9
+ VPATH = $(srcdir):$(topdir):$(hdrdir)
10
+ exec_prefix = $(prefix)
11
+ prefix = $(DESTDIR)/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr
12
+ sharedstatedir = $(prefix)/com
13
+ mandir = $(DESTDIR)/usr/share/man
14
+ psdir = $(docdir)
15
+ oldincludedir = $(DESTDIR)/usr/include
16
+ localedir = $(datarootdir)/locale
17
+ bindir = $(exec_prefix)/bin
18
+ libexecdir = $(exec_prefix)/libexec
19
+ sitedir = $(DESTDIR)/Library/Ruby/Site
20
+ htmldir = $(docdir)
21
+ vendorarchdir = $(vendorlibdir)/$(sitearch)
22
+ includedir = $(prefix)/include
23
+ infodir = $(DESTDIR)/usr/share/info
24
+ vendorlibdir = $(vendordir)/$(ruby_version)
25
+ sysconfdir = $(prefix)/etc
26
+ libdir = $(exec_prefix)/lib
27
+ sbindir = $(exec_prefix)/sbin
28
+ rubylibdir = $(libdir)/ruby/$(ruby_version)
29
+ docdir = $(datarootdir)/doc/$(PACKAGE)
30
+ dvidir = $(docdir)
31
+ vendordir = $(libdir)/ruby/vendor_ruby
32
+ datarootdir = $(prefix)/share
33
+ pdfdir = $(docdir)
34
+ archdir = $(rubylibdir)/$(arch)
35
+ sitearchdir = $(sitelibdir)/$(sitearch)
36
+ datadir = $(datarootdir)
37
+ localstatedir = $(prefix)/var
38
+ sitelibdir = $(sitedir)/$(ruby_version)
39
+
40
+ CC = gcc
41
+ LIBRUBY = $(LIBRUBY_SO)
42
+ LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
43
+ LIBRUBYARG_SHARED =
44
+ LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)
45
+
46
+ RUBY_EXTCONF_H =
47
+ CFLAGS = -fno-common -arch i386 -arch x86_64 -g -Os -pipe -fno-common -DENABLE_DTRACE -fno-common -pipe -fno-common $(cflags)
48
+ INCFLAGS = -I. -I$(topdir) -I$(hdrdir) -I$(srcdir)
49
+ DEFS =
50
+ CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE $(DEFS) $(cppflags)
51
+ CXXFLAGS = $(CFLAGS)
52
+ ldflags = -L. -arch i386 -arch x86_64 -framework CoreFoundation -undefined suppress -flat_namespace
53
+ dldflags =
54
+ archflag =
55
+ DLDFLAGS = $(ldflags) $(dldflags) $(archflag)
56
+ LDSHARED = cc -arch i386 -arch x86_64 -pipe -bundle -undefined dynamic_lookup
57
+ AR = ar
58
+ EXEEXT =
59
+
60
+ RUBY_INSTALL_NAME = ruby
61
+ RUBY_SO_NAME = ruby
62
+ arch = universal-darwin10.0
63
+ sitearch = universal-darwin10.0
64
+ ruby_version = 1.8
65
+ ruby = /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
66
+ RUBY = $(ruby)
67
+ RM = rm -f
68
+ MAKEDIRS = mkdir -p
69
+ INSTALL = /usr/bin/install -c
70
+ INSTALL_PROG = $(INSTALL) -m 0755
71
+ INSTALL_DATA = $(INSTALL) -m 644
72
+ COPY = cp
73
+
74
+ #### End of system configuration section. ####
75
+
76
+ preload =
77
+
78
+ libpath = . $(libdir)
79
+ LIBPATH = -L. -L$(libdir)
80
+ DEFFILE =
81
+
82
+ CLEANFILES = mkmf.log
83
+ DISTCLEANFILES =
84
+
85
+ extout =
86
+ extout_prefix =
87
+ target_prefix = /plist4r/backend/osx_plist/ext
88
+ LOCAL_LIBS =
89
+ LIBS = $(LIBRUBYARG_SHARED) -lpthread -ldl
90
+ SRCS = plist.c
91
+ OBJS = plist.o
92
+ TARGET = osx_plist
93
+ DLLIB = $(TARGET).bundle
94
+ EXTSTATIC =
95
+ STATIC_LIB =
96
+
97
+ BINDIR = $(bindir)
98
+ RUBYCOMMONDIR = $(sitedir)$(target_prefix)
99
+ RUBYLIBDIR = $(sitelibdir)$(target_prefix)
100
+ RUBYARCHDIR = $(sitearchdir)$(target_prefix)
101
+
102
+ TARGET_SO = $(DLLIB)
103
+ CLEANLIBS = $(TARGET).bundle $(TARGET).il? $(TARGET).tds $(TARGET).map
104
+ CLEANOBJS = *.o *.a *.s[ol] *.pdb *.exp *.bak
105
+
106
+ all: $(DLLIB)
107
+ static: $(STATIC_LIB)
108
+
109
+ clean:
110
+ @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
111
+
112
+ distclean: clean
113
+ @-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
114
+ @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
115
+
116
+ realclean: distclean
117
+ install: install-so install-rb
118
+
119
+ install-so: $(RUBYARCHDIR)
120
+ install-so: $(RUBYARCHDIR)/$(DLLIB)
121
+ $(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
122
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
123
+ install-rb: pre-install-rb install-rb-default
124
+ install-rb-default: pre-install-rb-default
125
+ pre-install-rb: Makefile
126
+ pre-install-rb-default: Makefile
127
+ $(RUBYARCHDIR):
128
+ $(MAKEDIRS) $@
129
+
130
+ site-install: site-install-so site-install-rb
131
+ site-install-so: install-so
132
+ site-install-rb: install-rb
133
+
134
+ .SUFFIXES: .c .m .cc .cxx .cpp .C .o
135
+
136
+ .cc.o:
137
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
138
+
139
+ .cxx.o:
140
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
141
+
142
+ .cpp.o:
143
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
144
+
145
+ .C.o:
146
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
147
+
148
+ .c.o:
149
+ $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) -c $<
150
+
151
+ $(DLLIB): $(OBJS) Makefile
152
+ @-$(RM) $@
153
+ $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
154
+
155
+
156
+
157
+ $(OBJS): ruby.h defines.h
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/ruby
2
+
3
+ if File.exists? "/System/Library/Frameworks/CoreFoundation.framework"
4
+ require 'mkmf'
5
+ $LDFLAGS += ' -framework CoreFoundation -undefined suppress -flat_namespace'
6
+ $LIBRUBYARG_SHARED=""
7
+ create_makefile("plist4r/backend/osx_plist/ext/osx_plist")
8
+ end
9
+
@@ -0,0 +1,606 @@
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 mPlist4r;
62
+ static VALUE mBackend;
63
+ static VALUE mOsxPlist;
64
+
65
+
66
+ static VALUE timeEpoch;
67
+ static VALUE ePropertyListError;
68
+
69
+ static VALUE id_gm;
70
+ static VALUE id_plus;
71
+ static VALUE id_minus;
72
+ static VALUE id_read;
73
+ static VALUE id_write;
74
+
75
+ static VALUE id_xml;
76
+ static VALUE id_binary;
77
+ static VALUE id_openstep;
78
+
79
+ static VALUE id_blob;
80
+
81
+ VALUE convertPropertyListRef(CFPropertyListRef plist);
82
+ VALUE convertStringRef(CFStringRef plist);
83
+ VALUE convertDictionaryRef(CFDictionaryRef plist);
84
+ VALUE convertArrayRef(CFArrayRef plist);
85
+ VALUE convertNumberRef(CFNumberRef plist);
86
+ VALUE convertBooleanRef(CFBooleanRef plist);
87
+ VALUE convertDataRef(CFDataRef plist);
88
+ VALUE convertDateRef(CFDateRef plist);
89
+ VALUE str_blob(VALUE self);
90
+ VALUE str_setBlob(VALUE self, VALUE b);
91
+
92
+ // Raises a Ruby exception with the given string
93
+ void raiseError(CFStringRef error) {
94
+ char *errBuffer = (char *)CFStringGetCStringPtr(error, kCFStringEncodingUTF8);
95
+ int freeBuffer = 0;
96
+ if (!errBuffer) {
97
+ int len = CFStringGetLength(error)*2+1;
98
+ errBuffer = ALLOC_N(char, len);
99
+ Boolean succ = CFStringGetCString(error, errBuffer, len, kCFStringEncodingUTF8);
100
+ if (!succ) {
101
+ CFStringGetCString(error, errBuffer, len, kCFStringEncodingMacRoman);
102
+ }
103
+ freeBuffer = 1;
104
+ }
105
+ rb_raise(ePropertyListError, (char *)errBuffer);
106
+ if (freeBuffer) free(errBuffer);
107
+ }
108
+
109
+ /* call-seq:
110
+ * load(obj, format = false)
111
+ *
112
+ * Loads a property list from an IO stream or a String and creates
113
+ * an equivalent Object from it.
114
+ *
115
+ * If +format+ is +true+, it returns an array of <tt>[object, format]</tt>
116
+ * where +format+ is one of <tt>:xml1</tt>, <tt>:binary1</tt>, or <tt>:openstep</tt>.
117
+ */
118
+ VALUE plist_load(int argc, VALUE *argv, VALUE self) {
119
+ VALUE io, retFormat;
120
+ int count = rb_scan_args(argc, argv, "11", &io, &retFormat);
121
+ if (count < 2) retFormat = Qfalse;
122
+ VALUE buffer;
123
+ if (RTEST(rb_respond_to(io, id_read))) {
124
+ // Read from IO
125
+ buffer = rb_funcall(io, id_read, 0);
126
+ } else {
127
+ StringValue(io);
128
+ buffer = io;
129
+ }
130
+ // For some reason, the CFReadStream version doesn't work with input < 6 characters
131
+ // but the CFDataRef version doesn't return format
132
+ // So lets use the CFDataRef version unless format is requested
133
+ CFStringRef error = NULL;
134
+ CFPropertyListRef plist;
135
+ CFPropertyListFormat format;
136
+ if (RTEST(retFormat)) {
137
+ // Format was requested
138
+ // now just in case, if the input is < 6 characters, we will pad it out with newlines
139
+ // we could do this in all cases, but I don't think it will work with binary
140
+ // even though binary shouldn't be < 6 characters
141
+ UInt8 *bytes;
142
+ int len;
143
+ if (RSTRING_LEN(buffer) < 6) {
144
+ bytes = ALLOC_N(UInt8, 6);
145
+ memset(bytes, '\n', 6);
146
+ MEMCPY(bytes, RSTRING_PTR(buffer), UInt8, RSTRING_LEN(buffer));
147
+ len = 6;
148
+ } else {
149
+ bytes = (UInt8 *)RSTRING_PTR(buffer);
150
+ len = RSTRING_LEN(buffer);
151
+ }
152
+ CFReadStreamRef readStream = CFReadStreamCreateWithBytesNoCopy(kCFAllocatorDefault, bytes, len, kCFAllocatorNull);
153
+ CFReadStreamOpen(readStream);
154
+ plist = CFPropertyListCreateFromStream(kCFAllocatorDefault, readStream, 0, kCFPropertyListImmutable, &format, &error);
155
+ CFReadStreamClose(readStream);
156
+ CFRelease(readStream);
157
+ } else {
158
+ // Format wasn't requested
159
+ CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8*)RSTRING_PTR(buffer), RSTRING_LEN(buffer), kCFAllocatorNull);
160
+ plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, kCFPropertyListImmutable, &error);
161
+ CFRelease(data);
162
+ }
163
+ if (error) {
164
+ raiseError(error);
165
+ CFRelease(error);
166
+ return Qnil;
167
+ }
168
+ VALUE obj = convertPropertyListRef(plist);
169
+ CFRelease(plist);
170
+ if (RTEST(retFormat)) {
171
+ VALUE ary = rb_ary_new();
172
+ rb_ary_push(ary, obj);
173
+ if (format == kCFPropertyListOpenStepFormat) {
174
+ retFormat = id_openstep;
175
+ } else if (format == kCFPropertyListXMLFormat_v1_0) {
176
+ retFormat = id_xml;
177
+ } else if (format == kCFPropertyListBinaryFormat_v1_0) {
178
+ retFormat = id_binary;
179
+ } else {
180
+ retFormat = rb_intern("unknown");
181
+ }
182
+ rb_ary_push(ary, ID2SYM(retFormat));
183
+ return ary;
184
+ } else {
185
+ return obj;
186
+ }
187
+ }
188
+
189
+ // Maps the property list object to a ruby object
190
+ VALUE convertPropertyListRef(CFPropertyListRef plist) {
191
+ CFTypeID typeID = CFGetTypeID(plist);
192
+ if (typeID == CFStringGetTypeID()) {
193
+ return convertStringRef((CFStringRef)plist);
194
+ } else if (typeID == CFDictionaryGetTypeID()) {
195
+ return convertDictionaryRef((CFDictionaryRef)plist);
196
+ } else if (typeID == CFArrayGetTypeID()) {
197
+ return convertArrayRef((CFArrayRef)plist);
198
+ } else if (typeID == CFNumberGetTypeID()) {
199
+ return convertNumberRef((CFNumberRef)plist);
200
+ } else if (typeID == CFBooleanGetTypeID()) {
201
+ return convertBooleanRef((CFBooleanRef)plist);
202
+ } else if (typeID == CFDataGetTypeID()) {
203
+ return convertDataRef((CFDataRef)plist);
204
+ } else if (typeID == CFDateGetTypeID()) {
205
+ return convertDateRef((CFDateRef)plist);
206
+ } else {
207
+ return Qnil;
208
+ }
209
+ }
210
+
211
+ // Converts a CFStringRef to a String
212
+ VALUE convertStringRef(CFStringRef plist) {
213
+ CFIndex byteCount;
214
+ CFRange range = CFRangeMake(0, CFStringGetLength(plist));
215
+ CFStringEncoding enc = kCFStringEncodingUTF8;
216
+ Boolean succ = CFStringGetBytes(plist, range, enc, 0, false, NULL, 0, &byteCount);
217
+ if (!succ) {
218
+ enc = kCFStringEncodingMacRoman;
219
+ CFStringGetBytes(plist, range, enc, 0, false, NULL, 0, &byteCount);
220
+ }
221
+ UInt8 *buffer = ALLOC_N(UInt8, byteCount);
222
+ CFStringGetBytes(plist, range, enc, 0, false, buffer, byteCount, NULL);
223
+ VALUE retval = rb_str_new((char *)buffer, (long)byteCount);
224
+ free(buffer);
225
+ return retval;
226
+ }
227
+
228
+ // Converts the keys and values of a CFDictionaryRef
229
+ void dictionaryConverter(const void *key, const void *value, void *context) {
230
+ rb_hash_aset((VALUE)context, convertPropertyListRef(key), convertPropertyListRef(value));
231
+ }
232
+
233
+ // Converts a CFDictionaryRef to a Hash
234
+ VALUE convertDictionaryRef(CFDictionaryRef plist) {
235
+ VALUE hash = rb_hash_new();
236
+ CFDictionaryApplyFunction(plist, dictionaryConverter, (void *)hash);
237
+ return hash;
238
+ }
239
+
240
+ // Converts the values of a CFArrayRef
241
+ void arrayConverter(const void *value, void *context) {
242
+ rb_ary_push((VALUE)context, convertPropertyListRef(value));
243
+ }
244
+
245
+ // Converts a CFArrayRef to an Array
246
+ VALUE convertArrayRef(CFArrayRef plist) {
247
+ VALUE array = rb_ary_new();
248
+ CFRange range = CFRangeMake(0, CFArrayGetCount(plist));
249
+ CFArrayApplyFunction(plist, range, arrayConverter, (void *)array);
250
+ return array;
251
+ }
252
+
253
+ // Converts a CFNumberRef to a Number
254
+ VALUE convertNumberRef(CFNumberRef plist) {
255
+ if (CFNumberIsFloatType(plist)) {
256
+ double val;
257
+ CFNumberGetValue(plist, kCFNumberDoubleType, &val);
258
+ return rb_float_new(val);
259
+ } else {
260
+ #ifdef LL2NUM
261
+ long long val;
262
+ CFNumberGetValue(plist, kCFNumberLongLongType, &val);
263
+ return LL2NUM(val);
264
+ #else
265
+ long val;
266
+ CFNumberGetValue(plist, kCFNumberLongType, &val);
267
+ return LONG2NUM(val);
268
+ #endif
269
+ }
270
+ }
271
+
272
+ // Converts a CFBooleanRef to a Boolean
273
+ VALUE convertBooleanRef(CFBooleanRef plist) {
274
+ if (CFBooleanGetValue(plist)) {
275
+ return Qtrue;
276
+ } else {
277
+ return Qfalse;
278
+ }
279
+ }
280
+
281
+ // Converts a CFDataRef to a String (with blob set to true)
282
+ VALUE convertDataRef(CFDataRef plist) {
283
+ const UInt8 *bytes = CFDataGetBytePtr(plist);
284
+ CFIndex len = CFDataGetLength(plist);
285
+ VALUE str = rb_str_new((char *)bytes, (long)len);
286
+ str_setBlob(str, Qtrue);
287
+ return str;
288
+ }
289
+
290
+ // Converts a CFDateRef to a Time
291
+ VALUE convertDateRef(CFDateRef plist) {
292
+ CFAbsoluteTime seconds = CFDateGetAbsoluteTime(plist);
293
+
294
+ // trunace the time since Ruby's Time object stores it as a 32 bit signed offset from 1970 (undocumented)
295
+ const float min_time = -3124310400.0f;
296
+ const float max_time = 1169098047.0f;
297
+ seconds = seconds < min_time ? min_time : (seconds > max_time ? max_time : seconds);
298
+
299
+ return rb_funcall(timeEpoch, id_plus, 1, rb_float_new(seconds));
300
+ }
301
+
302
+ CFPropertyListRef convertObject(VALUE obj);
303
+
304
+ // Converts a PropertyList object to a string representation
305
+ VALUE convertPlistToString(CFPropertyListRef plist, CFPropertyListFormat format) {
306
+ CFWriteStreamRef writeStream = CFWriteStreamCreateWithAllocatedBuffers(kCFAllocatorDefault, kCFAllocatorDefault);
307
+ CFWriteStreamOpen(writeStream);
308
+ CFStringRef error = NULL;
309
+ CFPropertyListWriteToStream(plist, writeStream, format, &error);
310
+ CFWriteStreamClose(writeStream);
311
+ if (error) {
312
+ raiseError(error);
313
+ return Qnil;
314
+ }
315
+ CFDataRef data = CFWriteStreamCopyProperty(writeStream, kCFStreamPropertyDataWritten);
316
+ CFRelease(writeStream);
317
+ VALUE plistData = convertDataRef(data);
318
+ CFRelease(data);
319
+ return plistData;
320
+ }
321
+
322
+ /* call-seq:
323
+ * dump(io, obj, format = :xml1)
324
+ *
325
+ * Writes the property list representation of +obj+
326
+ * to the IO stream (must be open for writing).
327
+ *
328
+ * +format+ can be one of <tt>:xml1</tt> or <tt>:binary1</tt>.
329
+ *
330
+ * Returns the number of bytes written, or +nil+ if
331
+ * the object could not be represented as a property list
332
+ */
333
+ VALUE plist_dump(int argc, VALUE *argv, VALUE self) {
334
+ VALUE io, obj, type;
335
+ int count = rb_scan_args(argc, argv, "21", &io, &obj, &type);
336
+ if (count < 3) {
337
+ type = id_xml;
338
+ } else {
339
+ type = rb_to_id(type);
340
+ }
341
+ if (!RTEST(rb_respond_to(io, id_write))) {
342
+ rb_raise(rb_eArgError, "Argument 1 must be an IO object");
343
+ return Qnil;
344
+ }
345
+ CFPropertyListFormat format;
346
+ if (type == id_xml) {
347
+ format = kCFPropertyListXMLFormat_v1_0;
348
+ } else if (type == id_binary) {
349
+ format = kCFPropertyListBinaryFormat_v1_0;
350
+ } else if (type == id_openstep) {
351
+ format = kCFPropertyListOpenStepFormat;
352
+ } else {
353
+ rb_raise(rb_eArgError, "Argument 3 must be one of :xml1, :binary1, or :openstep");
354
+ return Qnil;
355
+ }
356
+ CFPropertyListRef plist = convertObject(obj);
357
+ VALUE data = convertPlistToString(plist, format);
358
+ if (NIL_P(data)) {
359
+ return Qnil;
360
+ } else {
361
+ return rb_funcall(io, id_write, 1, data);
362
+ }
363
+ }
364
+
365
+ /* call-seq:
366
+ * object.to_plist(format = :xml1)
367
+ *
368
+ * Converts the object to a property list representation
369
+ * and returns it as a string.
370
+ *
371
+ * +format+ can be one of <tt>:xml1</tt> or <tt>:binary1</tt>.
372
+ */
373
+ VALUE obj_to_plist(int argc, VALUE *argv, VALUE self) {
374
+ VALUE type;
375
+ int count = rb_scan_args(argc, argv, "01", &type);
376
+ if (count < 1) {
377
+ type = id_xml;
378
+ } else {
379
+ type = rb_to_id(type);
380
+ }
381
+ CFPropertyListFormat format;
382
+ if (type == id_xml) {
383
+ format = kCFPropertyListXMLFormat_v1_0;
384
+ } else if (type == id_binary) {
385
+ format = kCFPropertyListBinaryFormat_v1_0;
386
+ } else if (type == id_openstep) {
387
+ format = kCFPropertyListOpenStepFormat;
388
+ } else {
389
+ rb_raise(rb_eArgError, "Argument 2 must be one of :xml1, :binary1, or :openstep");
390
+ return Qnil;
391
+ }
392
+ CFPropertyListRef plist = convertObject(self);
393
+ VALUE data = convertPlistToString(plist, format);
394
+ CFRelease(plist);
395
+ if (type == id_xml || type == id_binary) {
396
+ str_setBlob(data, Qfalse);
397
+ }
398
+ return data;
399
+ }
400
+
401
+ CFPropertyListRef convertString(VALUE obj);
402
+ CFDictionaryRef convertHash(VALUE obj);
403
+ CFArrayRef convertArray(VALUE obj);
404
+ CFNumberRef convertNumber(VALUE obj);
405
+ CFDateRef convertTime(VALUE obj);
406
+
407
+ // Converts an Object to a CFTypeRef
408
+ CFPropertyListRef convertObject(VALUE obj) {
409
+ switch (TYPE(obj)) {
410
+ case T_STRING: return convertString(obj); break;
411
+ case T_HASH: return convertHash(obj); break;
412
+ case T_ARRAY: return convertArray(obj); break;
413
+ case T_FLOAT:
414
+ case T_FIXNUM:
415
+ case T_BIGNUM: return convertNumber(obj); break;
416
+ case T_TRUE: return kCFBooleanTrue; break;
417
+ case T_FALSE: return kCFBooleanFalse; break;
418
+ default: if (rb_obj_is_kind_of(obj, rb_cTime)) return convertTime(obj);
419
+ }
420
+ rb_raise(rb_eArgError, "An object in the argument tree could not be converted");
421
+ return NULL;
422
+ }
423
+
424
+ // Converts a String to a CFStringRef
425
+ CFPropertyListRef convertString(VALUE obj) {
426
+ if (RTEST(str_blob(obj))) {
427
+ // convert to CFDataRef
428
+ StringValue(obj);
429
+ CFDataRef data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)RSTRING_PTR(obj), (CFIndex)RSTRING_LEN(obj));
430
+ return data;
431
+ } else {
432
+ // convert to CFStringRef
433
+ StringValue(obj);
434
+ CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)RSTRING_PTR(obj), (CFIndex)RSTRING_LEN(obj), kCFStringEncodingUTF8, false);
435
+ if (!string) {
436
+ // try MacRoman
437
+ string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)RSTRING_PTR(obj), (CFIndex)RSTRING_LEN(obj), kCFStringEncodingMacRoman, false);
438
+ }
439
+ return string;
440
+ }
441
+ }
442
+
443
+ // Converts the keys and values of a Hash to CFTypeRefs
444
+ int iterateHash(VALUE key, VALUE val, VALUE dict) {
445
+ CFPropertyListRef dKey = convertObject(key);
446
+ CFPropertyListRef dVal = convertObject(val);
447
+ CFDictionaryAddValue((CFMutableDictionaryRef)dict, dKey, dVal);
448
+ CFRelease(dKey);
449
+ CFRelease(dVal);
450
+ return ST_CONTINUE;
451
+ }
452
+
453
+ // Converts a Hash to a CFDictionaryREf
454
+ CFDictionaryRef convertHash(VALUE obj) {
455
+ // RHASH_TBL exists in ruby 1.8.7 but not ruby 1.8.6
456
+ #ifdef RHASH_TBL
457
+ st_table *tbl = RHASH_TBL(obj);
458
+ #else
459
+ st_table *tbl = RHASH(obj)->tbl;
460
+ #endif
461
+ CFIndex count = (CFIndex)tbl->num_entries;
462
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
463
+ st_foreach(tbl, iterateHash, (VALUE)dict);
464
+ return dict;
465
+ }
466
+
467
+ // Converts an Array to a CFArrayRef
468
+ CFArrayRef convertArray(VALUE obj) {
469
+ CFIndex count = (CFIndex)RARRAY_LEN(obj);
470
+ CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, count, &kCFTypeArrayCallBacks);
471
+ int i;
472
+ for (i = 0; i < count; i++) {
473
+ CFPropertyListRef aVal = convertObject(RARRAY_PTR(obj)[i]);
474
+ CFArrayAppendValue(array, aVal);
475
+ CFRelease(aVal);
476
+ }
477
+ return array;
478
+ }
479
+
480
+ // Converts a Number to a CFNumberRef
481
+ CFNumberRef convertNumber(VALUE obj) {
482
+ void *valuePtr;
483
+ CFNumberType type;
484
+ switch (TYPE(obj)) {
485
+ case T_FLOAT: {
486
+ double num = NUM2DBL(obj);
487
+ valuePtr = &num;
488
+ type = kCFNumberDoubleType;
489
+ break;
490
+ }
491
+ case T_FIXNUM: {
492
+ int num = NUM2INT(obj);
493
+ valuePtr = &num;
494
+ type = kCFNumberIntType;
495
+ break;
496
+ }
497
+ case T_BIGNUM: {
498
+ #ifdef NUM2LL
499
+ long long num = NUM2LL(obj);
500
+ type = kCFNumberLongLongType;
501
+ #else
502
+ long num = NUM2LONG(obj);
503
+ type = kCFNumberLongType;
504
+ #endif
505
+ valuePtr = &num;
506
+ break;
507
+ }
508
+ default:
509
+ rb_raise(rb_eStandardError, "ERROR: Wrong object type passed to convertNumber");
510
+ return NULL;
511
+ }
512
+ CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, type, valuePtr);
513
+ return number;
514
+ }
515
+
516
+ // Converts a Time to a CFDateRef
517
+ CFDateRef convertTime(VALUE obj) {
518
+ VALUE secs = rb_funcall(obj, id_minus, 1, timeEpoch);
519
+ CFDateRef date = CFDateCreate(kCFAllocatorDefault, NUM2DBL(secs));
520
+ return date;
521
+ }
522
+
523
+ /* call-seq:
524
+ * str.blob?
525
+ *
526
+ * Returns whether or not +str+ is a blob.
527
+ */
528
+ VALUE str_blob(VALUE self) {
529
+ VALUE blob = rb_attr_get(self, id_blob);
530
+ if (NIL_P(blob)) {
531
+ return Qfalse;
532
+ } else {
533
+ return blob;
534
+ }
535
+ }
536
+
537
+ /* call-seq:
538
+ * str.blob = bool
539
+ *
540
+ * Sets the blob status of +str+.
541
+ */
542
+ VALUE str_setBlob(VALUE self, VALUE b) {
543
+ if (TYPE(b) == T_TRUE || TYPE(b) == T_FALSE) {
544
+ return rb_ivar_set(self, id_blob, b);
545
+ } else {
546
+ rb_raise(rb_eArgError, "Argument 1 must be true or false");
547
+ return Qnil;
548
+ }
549
+ }
550
+
551
+ /*
552
+ * Document-module: OSX
553
+ */
554
+
555
+ /*
556
+ * Document-module: OSX::PropertyList
557
+ *
558
+ * The PropertyList module provides a means of converting a
559
+ * Ruby Object to a Property List.
560
+ *
561
+ * The various Objects that can be converted are the ones
562
+ * with an equivalent in CoreFoundation. This includes: String,
563
+ * Integer, Float, Boolean, Time, Hash, and Array.
564
+ *
565
+ * See also: String#blob?, String#blob=, and Object#to_plist
566
+ */
567
+
568
+ /*
569
+ * Document-class: OSX::PropertyListError
570
+ */
571
+ void Init_osx_plist() {
572
+ mPlist4r = rb_define_module("Plist4r");
573
+
574
+ if (rb_const_defined_at(mPlist4r, rb_intern("Backend")))
575
+ mBackend = rb_const_get_at(mPlist4r, rb_intern("Backend"));
576
+ else
577
+ mBackend = rb_define_class_under(mPlist4r, "Backend", 0);
578
+
579
+ if (rb_const_defined_at(mBackend, rb_intern("OsxPlist")))
580
+ mOsxPlist = rb_const_get_at(mBackend, rb_intern("OsxPlist"));
581
+ else
582
+ mOsxPlist = rb_define_module_under(mBackend, "OsxPlist");
583
+
584
+ rb_define_module_function(mOsxPlist, "load", plist_load, -1);
585
+ rb_define_module_function(mOsxPlist, "dump", plist_dump, -1);
586
+
587
+ rb_define_method(rb_cObject, "to_plist", obj_to_plist, -1);
588
+ rb_define_method(rb_cString, "blob?", str_blob, 0);
589
+ rb_define_method(rb_cString, "blob=", str_setBlob, 1);
590
+
591
+ ePropertyListError = rb_define_class_under(mOsxPlist, "PropertyListError", rb_eStandardError);
592
+
593
+ id_gm = rb_intern("gm");
594
+ timeEpoch = rb_funcall(rb_cTime, id_gm, 1, INT2FIX(2001));
595
+ /* Time.gm(2001): The Cocoa epoch of January 1st, 2001*/
596
+ // rb_define_const(mPlist, "EPOCH", timeEpoch);
597
+ rb_define_const(mOsxPlist, "EPOCH", timeEpoch);
598
+ id_plus = rb_intern("+");
599
+ id_minus = rb_intern("-");
600
+ id_read = rb_intern("read");
601
+ id_write = rb_intern("write");
602
+ id_xml = rb_intern("xml1");
603
+ id_binary = rb_intern("binary1");
604
+ id_openstep = rb_intern("openstep");
605
+ id_blob = rb_intern("@blob");
606
+ }