plist4r 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,168 @@
1
+ #--
2
+ # Copyright (c) 2005 David Hansson,
3
+ # Copyright (c) 2007 Mauricio Fernandez, Sam Stephenson
4
+ # Copyright (c) 2008 Steve Purcell, Josh Peek
5
+ # Copyright (c) 2009 Christoffer Sawicki
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #++
26
+
27
+ # OrderedHash is namespaced to prevent conflicts with other implementations
28
+ module ActiveSupport
29
+ # Hash is ordered in Ruby 1.9!
30
+ if RUBY_VERSION >= '1.9'
31
+ class OrderedHash < ::Hash #:nodoc:
32
+ end
33
+ else
34
+ class OrderedHash < Hash #:nodoc:
35
+ def initialize(*args, &block)
36
+ super
37
+ @keys = []
38
+ end
39
+
40
+ def self.[](*args)
41
+ ordered_hash = new
42
+
43
+ if (args.length == 1 && args.first.is_a?(Array))
44
+ args.first.each do |key_value_pair|
45
+ next unless (key_value_pair.is_a?(Array))
46
+ ordered_hash[key_value_pair[0]] = key_value_pair[1]
47
+ end
48
+
49
+ return ordered_hash
50
+ end
51
+
52
+ unless (args.size % 2 == 0)
53
+ raise ArgumentError.new("odd number of arguments for Hash")
54
+ end
55
+
56
+ args.each_with_index do |val, ind|
57
+ next if (ind % 2 != 0)
58
+ ordered_hash[val] = args[ind + 1]
59
+ end
60
+
61
+ ordered_hash
62
+ end
63
+
64
+ def initialize_copy(other)
65
+ super
66
+ # make a deep copy of keys
67
+ @keys = other.keys
68
+ end
69
+
70
+ def []=(key, value)
71
+ @keys << key if !has_key?(key)
72
+ super
73
+ end
74
+
75
+ def delete(key)
76
+ if has_key? key
77
+ index = @keys.index(key)
78
+ @keys.delete_at index
79
+ end
80
+ super
81
+ end
82
+
83
+ def delete_if
84
+ super
85
+ sync_keys!
86
+ self
87
+ end
88
+
89
+ def reject!
90
+ super
91
+ sync_keys!
92
+ self
93
+ end
94
+
95
+ def reject(&block)
96
+ dup.reject!(&block)
97
+ end
98
+
99
+ def keys
100
+ @keys.dup
101
+ end
102
+
103
+ def values
104
+ @keys.collect { |key| self[key] }
105
+ end
106
+
107
+ def to_hash
108
+ self
109
+ end
110
+
111
+ def to_a
112
+ @keys.map { |key| [ key, self[key] ] }
113
+ end
114
+
115
+ def each_key
116
+ @keys.each { |key| yield key }
117
+ end
118
+
119
+ def each_value
120
+ @keys.each { |key| yield self[key]}
121
+ end
122
+
123
+ def each
124
+ @keys.each {|key| yield [key, self[key]]}
125
+ end
126
+
127
+ alias_method :each_pair, :each
128
+
129
+ def clear
130
+ super
131
+ @keys.clear
132
+ self
133
+ end
134
+
135
+ def shift
136
+ k = @keys.first
137
+ v = delete(k)
138
+ [k, v]
139
+ end
140
+
141
+ def merge!(other_hash)
142
+ other_hash.each {|k,v| self[k] = v }
143
+ self
144
+ end
145
+
146
+ def merge(other_hash)
147
+ dup.merge!(other_hash)
148
+ end
149
+
150
+ # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
151
+ def replace(other)
152
+ super
153
+ @keys = other.keys
154
+ self
155
+ end
156
+
157
+ def inspect
158
+ "#<OrderedHash #{super}>"
159
+ end
160
+
161
+ private
162
+
163
+ def sync_keys!
164
+ @keys.delete_if {|k| !has_key?(k)}
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,193 @@
1
+
2
+
3
+ module Plist4r::Popen4
4
+
5
+ # This is taken directly from Ara T Howard's Open4 library, and then
6
+ # modified to suit the needs of Chef. Any bugs here are most likely
7
+ # my own, and not Ara's.
8
+ #
9
+ # The original appears in external/open4.rb in its unmodified form.
10
+ #
11
+ # Thanks Ara!
12
+ def popen4(cmd, args={}, &b)
13
+
14
+ # Waitlast - this is magic.
15
+ #
16
+ # Do we wait for the child process to die before we yield
17
+ # to the block, or after? That is the magic of waitlast.
18
+ #
19
+ # By default, we are waiting before we yield the block.
20
+ args[:waitlast] ||= false
21
+
22
+ args[:user] ||= nil
23
+ unless args[:user].kind_of?(Integer)
24
+ args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
25
+ end
26
+ args[:group] ||= nil
27
+ unless args[:group].kind_of?(Integer)
28
+ args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
29
+ end
30
+ args[:environment] ||= {}
31
+
32
+ # Default on C locale so parsing commands output can be done
33
+ # independently of the node's default locale.
34
+ # "LC_ALL" could be set to nil, in which case we also must ignore it.
35
+ unless args[:environment].has_key?("LC_ALL")
36
+ args[:environment]["LC_ALL"] = "C"
37
+ end
38
+
39
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
40
+
41
+ verbose = $VERBOSE
42
+ begin
43
+ $VERBOSE = nil
44
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
45
+
46
+ cid = fork {
47
+ pw.last.close
48
+ STDIN.reopen pw.first
49
+ pw.first.close
50
+
51
+ pr.first.close
52
+ STDOUT.reopen pr.last
53
+ pr.last.close
54
+
55
+ pe.first.close
56
+ STDERR.reopen pe.last
57
+ pe.last.close
58
+
59
+ STDOUT.sync = STDERR.sync = true
60
+
61
+ if args[:group]
62
+ Process.egid = args[:group]
63
+ Process.gid = args[:group]
64
+ end
65
+
66
+ if args[:user]
67
+ Process.euid = args[:user]
68
+ Process.uid = args[:user]
69
+ end
70
+
71
+ args[:environment].each do |key,value|
72
+ ENV[key] = value
73
+ end
74
+
75
+ if args[:umask]
76
+ umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
77
+ File.umask(umask)
78
+ end
79
+
80
+ begin
81
+ if cmd.kind_of?(Array)
82
+ exec(*cmd)
83
+ else
84
+ exec(cmd)
85
+ end
86
+ raise 'forty-two'
87
+ rescue Exception => e
88
+ Marshal.dump(e, ps.last)
89
+ ps.last.flush
90
+ end
91
+ ps.last.close unless (ps.last.closed?)
92
+ exit!
93
+ }
94
+ ensure
95
+ $VERBOSE = verbose
96
+ end
97
+
98
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
99
+
100
+ begin
101
+ e = Marshal.load ps.first
102
+ raise(Exception === e ? e : "unknown failure!")
103
+ rescue EOFError # If we get an EOF error, then the exec was successful
104
+ 42
105
+ ensure
106
+ ps.first.close
107
+ end
108
+
109
+ pw.last.sync = true
110
+
111
+ pi = [pw.last, pr.first, pe.first]
112
+
113
+ if b
114
+ begin
115
+ if args[:waitlast]
116
+ b[cid, *pi]
117
+ # send EOF so that if the child process is reading from STDIN
118
+ # it will actually finish up and exit
119
+ pi[0].close_write
120
+ Process.waitpid2(cid).last
121
+ else
122
+ # This took some doing.
123
+ # The trick here is to close STDIN
124
+ # Then set our end of the childs pipes to be O_NONBLOCK
125
+ # Then wait for the child to die, which means any IO it
126
+ # wants to do must be done - it's dead. If it isn't,
127
+ # it's because something totally skanky is happening,
128
+ # and we don't care.
129
+ o = StringIO.new
130
+ e = StringIO.new
131
+
132
+ pi[0].close
133
+
134
+ stdout = pi[1]
135
+ stderr = pi[2]
136
+
137
+ stdout.sync = true
138
+ stderr.sync = true
139
+
140
+ stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
141
+ stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
142
+
143
+ stdout_finished = false
144
+ stderr_finished = false
145
+
146
+ results = nil
147
+
148
+ while !stdout_finished || !stderr_finished
149
+ begin
150
+ channels_to_watch = []
151
+ channels_to_watch << stdout if !stdout_finished
152
+ channels_to_watch << stderr if !stderr_finished
153
+ ready = IO.select(channels_to_watch, nil, nil, 1.0)
154
+ rescue Errno::EAGAIN
155
+ ensure
156
+ results = Process.waitpid2(cid, Process::WNOHANG)
157
+ if results
158
+ stdout_finished = true
159
+ stderr_finished = true
160
+ end
161
+ end
162
+
163
+ if ready && ready.first.include?(stdout)
164
+ line = results ? stdout.gets(nil) : stdout.gets
165
+ if line
166
+ o.write(line)
167
+ else
168
+ stdout_finished = true
169
+ end
170
+ end
171
+ if ready && ready.first.include?(stderr)
172
+ line = results ? stderr.gets(nil) : stderr.gets
173
+ if line
174
+ e.write(line)
175
+ else
176
+ stderr_finished = true
177
+ end
178
+ end
179
+ end
180
+ results = Process.waitpid2(cid) unless results
181
+ o.rewind
182
+ e.rewind
183
+ b[cid, pi[0], o, e]
184
+ results.last
185
+ end
186
+ ensure
187
+ pi.each{|fd| fd.close unless fd.closed?}
188
+ end
189
+ else
190
+ [cid, pw.last, pr.first, pe.first]
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,24 @@
1
+
2
+
3
+ class Object
4
+ def method_name
5
+ if /`(.*)'/.match(caller.first)
6
+ return $1
7
+ end
8
+ nil
9
+ end
10
+ end
11
+
12
+
13
+ class String
14
+ def camelcase
15
+ str = self.dup.capitalize.gsub(/[-_.\s]([a-zA-Z0-9])/) { $1.upcase } \
16
+ .gsub('+', 'x')
17
+ end
18
+
19
+ def snake_case
20
+ str = self.dup.gsub(/[A-Z]/) {|s| "_" + s}
21
+ str = str.downcase.sub(/^\_/, "")
22
+ end
23
+ end
24
+
@@ -0,0 +1,256 @@
1
+
2
+ require 'plist4r/mixin/ordered_hash'
3
+ require 'plist4r/plist_type'
4
+ require 'plist4r/backend'
5
+
6
+
7
+ class Plist4r::Plist
8
+ PlistOptionsHash = %w[from_string filename path file_format plist_type unsupported_keys]
9
+ FileFormats = %w[binary xml next_step]
10
+
11
+ def initialize *args, &blk
12
+ @hash = ::ActiveSupport::OrderedHash.new
13
+ @plist_cache = PlistCache.new self
14
+ @plist_type = plist_type PlistType::Plist
15
+ @unsupported_keys = Config[:unsupported_keys]
16
+ @from_string = nil
17
+ @filename = nil
18
+ @file_format = nil
19
+ @path = Config[:default_path]
20
+
21
+ case args.first
22
+ when Hash
23
+ parse_opts args.first
24
+
25
+ when String, Symbol
26
+ @filename = args.first.to_s
27
+ when nil
28
+ else
29
+ raise "Unrecognized first argument: #{args.first.inspect}"
30
+ end
31
+ end
32
+
33
+ def from_string string=nil
34
+ case string
35
+ when String
36
+ plist_format = ::Plist4r.string_detect_format string
37
+ if plist_format
38
+ eval "@plist_cache.from_#{format}, string"
39
+ else
40
+ raise "Unknown plist format for string: #{string}"
41
+ end
42
+ @from_string = string
43
+ when nil
44
+ @from_string
45
+ else
46
+ raise "Please specify a string of plist data"
47
+ end
48
+ end
49
+
50
+ def filename filename=nil
51
+ case filename
52
+ when String
53
+ @filename = filename
54
+ when nil
55
+ @filename
56
+ else
57
+ raise "Please specify a filename"
58
+ end
59
+ end
60
+
61
+ def path path=nil
62
+ case path
63
+ when String
64
+ @path = path
65
+ when nil
66
+ @path
67
+ else
68
+ raise "Please specify a directory"
69
+ end
70
+ end
71
+
72
+ def filename_path filename_path=nil
73
+ case path
74
+ when String
75
+ @filename = File.basename filename_path
76
+ @path = File.dirname filename_path
77
+ when nil
78
+ File.expand_path @filename, @path
79
+ else
80
+ raise "Please specify directory + filename"
81
+ end
82
+ end
83
+
84
+ def file_format file_format=nil
85
+ begin
86
+ case file_format
87
+ when Symbol, String
88
+ if FileFormats.include? file_format.to_s.snake_case
89
+ @file_format = file_format.to_s.snake_case
90
+ else
91
+ raise "Unrecognized plist file format: \"#{file_format.inspect}\". Please specify a valid plist file format, #{FileFormats.inspect}"
92
+ end
93
+ when nil
94
+ @file_format
95
+ else
96
+ raise "Please specify a valid plist file format, #{FileFormats.inspect}"
97
+ end
98
+ rescue
99
+ raise "Please specify a valid plist file format, #{FileFormats.inspect}"
100
+ end
101
+ end
102
+
103
+ def plist_type plist_type=nil
104
+ begin
105
+ case plist_type
106
+ when Class
107
+ @plist_type = PlistType::Plist.new :hash => @hash
108
+ when Symbol, String
109
+ eval "pt_klass = PlistType::#{plist_type.to_s.camelcase}"
110
+ @plist_type = pt_klass.new :hash => @hash
111
+ when nil
112
+ @plist_type
113
+ else
114
+ raise "Please specify a valid plist class name, eg ::Plist4r::PlistType::ClassName, \"class_name\" or :class_name"
115
+ end
116
+ rescue
117
+ raise "Please specify a valid plist class name, eg ::Plist4r::PlistType::ClassName, \"class_name\" or :class_name"
118
+ end
119
+ end
120
+
121
+ def unsupported_keys bool
122
+ case bool
123
+ when true,false
124
+ @unsupported_keys = bool
125
+ when nil
126
+ @unsupported_keys
127
+ else
128
+ raise "Please specify true or false to enable / disable this option"
129
+ end
130
+ end
131
+
132
+ def parse_opts opts
133
+ PlistOptionsHash.each do |opt|
134
+ eval "@#{opt} = #{opts[opt.to_sym]}" if opts[opt.to_sym]
135
+ end
136
+ end
137
+
138
+ def open filename=nil
139
+ @filename = filename if filename
140
+ raise "No filename specified" unless @filename
141
+ @hash = @plist_cache.open
142
+ end
143
+
144
+ def << *args, &blk
145
+ edit *args, &blk
146
+ end
147
+
148
+ def edit *args, &blk
149
+ instance_eval &blk
150
+ end
151
+
152
+ def method_missing method_sym, *args, &blk
153
+ @plist_type.send method_sym, *args, &blk
154
+ end
155
+
156
+ def import_hash hash=nil
157
+ case path
158
+ when ::ActiveSupport::OrderedHash
159
+ @hash = hash
160
+ when nil
161
+ @hash = ::ActiveSupport::OrderedHash.new
162
+ else
163
+ raise "Please use ::ActiveSupport::OrderedHash.new for your hashes"
164
+ end
165
+ end
166
+
167
+ def to_hash
168
+ @hash
169
+ end
170
+
171
+ def to_xml
172
+ @plist_cache.to_xml
173
+ end
174
+
175
+ def to_binary
176
+ @plist_cache.to_binary
177
+ end
178
+
179
+ def to_next_step
180
+ @plist_cache.to_next_step
181
+ end
182
+
183
+ def save
184
+ raise "No filename specified" unless @filename
185
+ @plist_cache.save
186
+ end
187
+
188
+ def save_as filename
189
+ @filename = filename
190
+ save
191
+ end
192
+ end
193
+
194
+
195
+
196
+ # plutil -convert xml1 @filename
197
+ # plutil -convert binary1 @filename
198
+
199
+
200
+ module Plist4r
201
+ class Plist
202
+ include ::Plist4r::Popen4
203
+
204
+ def initialize path_prefix, plist_str, &blk
205
+ plist_str << ".plist" unless plist_str =~ /\.plist$/
206
+
207
+ @filename = nil
208
+ if plist_str =~ /^\//
209
+ @filename = plist_str
210
+ else
211
+ @filename = "#{path_prefix}/#{plist_str}"
212
+ end
213
+
214
+ @label = @filename.match(/^.*\/(.*)\.plist$/)[1]
215
+ @shortname = @filename.match(/^.*\.(.*)$/)[1]
216
+
217
+ @block = blk
218
+ @hash = @orig = ::ActiveSupport::OrderedHash.new
219
+
220
+ instance_eval(&@block) if @block
221
+ end
222
+
223
+ def finalize
224
+ if File.exists? @filename
225
+ if override_plist_keys?
226
+ # @hash = @obj = ::LibxmlLaunchdPlistParser.new(@filename).plist_struct
227
+ # eval_plist_block(&@block) if @block
228
+ write_plist
229
+ end
230
+ else
231
+ write_plist
232
+ end
233
+ validate
234
+ end
235
+
236
+ def override_plist_keys?
237
+ return true unless @label == @filename.match(/^.*\/(.*)\.plist$/)[1]
238
+ vars = self.instance_variables - ["@filename","@label","@shortname","@block","@hash","@obj"]
239
+ return true unless vars.empty?
240
+ end
241
+
242
+ def write_plist
243
+ require 'haml'
244
+ engine = Haml::Engine.new File.read("launchd_plist.haml")
245
+ rendered_xml_output = engine.render self
246
+ File.open(@filename,'w') do |o|
247
+ o << rendered_xml_output
248
+ end
249
+
250
+ end
251
+
252
+ def validate
253
+ system "/usr/bin/plutil #{@filename}"
254
+ end
255
+ end
256
+ end