plist4r 0.2.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|