plist4r 0.0.0 → 0.1.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.
@@ -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