plist4r 0.2.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.yardopts +11 -0
- data/LICENSE +3 -1
- data/README.rdoc +25 -122
- data/Rakefile +14 -0
- data/VERSION +1 -1
- data/bin/plist4r +2 -0
- data/ext/osx_plist/Makefile +157 -0
- data/ext/osx_plist/extconf.rb +9 -0
- data/ext/osx_plist/plist.c +606 -0
- data/ext/osx_plist/plist.o +0 -0
- data/lib/plist4r.rb +6 -3
- data/lib/plist4r/application.rb +1 -2
- data/lib/plist4r/backend.rb +102 -34
- data/lib/plist4r/backend/c_f_property_list.rb +65 -0
- data/lib/plist4r/backend/c_f_property_list/LICENSE +19 -0
- data/lib/plist4r/backend/c_f_property_list/README +34 -0
- data/lib/plist4r/backend/c_f_property_list/cfpropertylist.rb +6 -0
- data/lib/plist4r/backend/c_f_property_list/rbBinaryCFPropertyList.rb +663 -0
- data/lib/plist4r/backend/c_f_property_list/rbCFPlistError.rb +26 -0
- data/lib/plist4r/backend/c_f_property_list/rbCFPropertyList.rb +348 -0
- data/lib/plist4r/backend/c_f_property_list/rbCFTypes.rb +241 -0
- data/lib/plist4r/backend/c_f_property_list/rbXMLCFPropertyList.rb +116 -0
- data/lib/plist4r/backend/example.rb +37 -52
- data/lib/plist4r/backend/haml.rb +47 -36
- data/lib/plist4r/backend/libxml4r.rb +24 -20
- data/lib/plist4r/backend/osx_plist.rb +82 -0
- data/lib/plist4r/backend/ruby_cocoa.rb +172 -54
- data/lib/plist4r/backend/test/data_types.rb +163 -0
- data/lib/plist4r/backend/test/harness.rb +255 -0
- data/lib/plist4r/backend/test/output.rb +47 -0
- data/lib/plist4r/backend_base.rb +4 -2
- data/lib/plist4r/{options.rb → cli.rb} +2 -1
- data/lib/plist4r/commands.rb +13 -8
- data/lib/plist4r/config.rb +36 -9
- data/lib/plist4r/docs/Backends.html +59 -0
- data/lib/plist4r/docs/DeveloperGuide.rdoc +53 -0
- data/lib/plist4r/docs/EditingPlistFiles.rdoc +88 -0
- data/lib/plist4r/docs/InfoPlistExample.rdoc +33 -0
- data/lib/plist4r/docs/LaunchdPlistExample.rdoc +33 -0
- data/lib/plist4r/docs/PlistKeyNames.rdoc +47 -0
- data/lib/plist4r/mixin/array_dict.rb +61 -0
- data/lib/plist4r/mixin/data_methods.rb +178 -54
- data/lib/plist4r/mixin/haml4r.rb +4 -0
- data/lib/plist4r/mixin/haml4r/css_attributes.rb +19 -0
- data/lib/plist4r/mixin/haml4r/examples.rb +261 -0
- data/lib/plist4r/mixin/haml4r/haml_table_example.rb +79 -0
- data/lib/plist4r/mixin/haml4r/table.rb +157 -0
- data/lib/plist4r/mixin/haml4r/table_cell.rb +160 -0
- data/lib/plist4r/mixin/haml4r/table_cells.rb +485 -0
- data/lib/plist4r/mixin/haml4r/table_section.rb +101 -0
- data/lib/plist4r/mixin/ordered_hash.rb +9 -1
- data/lib/plist4r/mixin/popen4.rb +1 -1
- data/lib/plist4r/mixin/ruby_stdlib.rb +154 -1
- data/lib/plist4r/mixin/script.rb +133 -0
- data/lib/plist4r/mixin/table.rb +435 -0
- data/lib/plist4r/plist.rb +272 -94
- data/lib/plist4r/plist_cache.rb +42 -43
- data/lib/plist4r/plist_type.rb +31 -74
- data/lib/plist4r/plist_type/info.rb +157 -3
- data/lib/plist4r/plist_type/launchd.rb +54 -48
- data/lib/plist4r/plist_type/plist.rb +1 -3
- data/plist4r.gemspec +74 -14
- data/spec/{examples.rb → launchd_examples.rb} +131 -139
- data/spec/plist4r/application_spec.rb +37 -0
- data/spec/plist4r/backend_spec.rb +256 -0
- data/spec/plist4r/cli_spec.rb +25 -0
- data/spec/plist4r/commands_spec.rb +20 -0
- data/spec/plist4r/config_spec.rb +38 -0
- data/spec/plist4r/mixin/array_dict_spec.rb +120 -0
- data/spec/plist4r/mixin/data_methods_spec.rb +96 -0
- data/spec/plist4r/mixin/haml4r/examples.rb +261 -0
- data/spec/plist4r/mixin/ruby_stdlib_spec.rb +228 -0
- data/spec/plist4r/plist_cache_spec.rb +261 -0
- data/spec/plist4r/plist_spec.rb +841 -23
- data/spec/plist4r/plist_type_spec.rb +126 -0
- data/spec/plist4r_spec.rb +53 -27
- data/spec/scratchpad.rb +226 -0
- data/spec/spec_helper.rb +5 -1
- metadata +109 -23
- data/lib/plist4r/backend/plutil.rb +0 -25
- data/lib/plist4r/mixin.rb +0 -7
- data/plists/array_mini.xml +0 -14
- data/plists/example_big_binary.plist +0 -0
- data/plists/example_medium_binary_launchd.plist +0 -0
- data/plists/example_medium_launchd.xml +0 -53
- data/plists/mini.xml +0 -12
- data/test.rb +0 -40
data/.gitignore
CHANGED
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
data/README.rdoc
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
= plist4r
|
|
2
2
|
|
|
3
|
-
Plist4r is a friendly ruby
|
|
3
|
+
Plist4r is a friendly rubygem / ruby lib for handling plist files.
|
|
4
4
|
|
|
5
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
==
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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)
|
|
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.
|
|
1
|
+
1.0.0
|
data/bin/plist4r
CHANGED
|
@@ -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 = #
|
|
488
|
+
type = kCFNumberDoubleType;
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case T_FIXNUM: {
|
|
492
|
+
int num = NUM2INT(obj);
|
|
493
|
+
valuePtr = #
|
|
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 = #
|
|
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
|
+
}
|