Codebase list facter / cb429df
Partial revert of CFPropertyList migration. While the new CFPropertyList implementation is great, the older plist implementation is used from a bunch of places in Puppet - including some package providers and other unexpected sources. Since the two projects share this code so intimately, we risk breaking more than we care about if we just eliminate this code. This restores the previous version of those files, allowing dependent code to gradually be migrated away to the newer implementation over time. Signed-off-by: Daniel Pittman <daniel@puppetlabs.com> Daniel Pittman 12 years ago
3 changed file(s) with 478 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 #--###########################################################
1 # Copyright 2006, Ben Bleything <ben@bleything.net> and #
2 # Patrick May <patrick@hexane.org> #
3 # #
4 # Distributed under the MIT license. #
5 ##############################################################
6 #++
7 # See Plist::Emit.
8 module Plist
9 # === Create a plist
10 # You can dump an object to a plist in one of two ways:
11 #
12 # * <tt>Plist::Emit.dump(obj)</tt>
13 # * <tt>obj.to_plist</tt>
14 # * This requires that you mixin the <tt>Plist::Emit</tt> module, which is already done for +Array+ and +Hash+.
15 #
16 # The following Ruby classes are converted into native plist types:
17 # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false
18 # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the <array> and <dict> containers (respectively).
19 # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a <data> element.
20 # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to <tt>Marshal.dump</tt> and the result placed in a <data> element.
21 #
22 # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below.
23 module Emit
24 # Helper method for injecting into classes. Calls <tt>Plist::Emit.dump</tt> with +self+.
25 def to_plist(envelope = true)
26 return Plist::Emit.dump(self, envelope)
27 end
28
29 # Helper method for injecting into classes. Calls <tt>Plist::Emit.save_plist</tt> with +self+.
30 def save_plist(filename)
31 Plist::Emit.save_plist(self, filename)
32 end
33
34 # The following Ruby classes are converted into native plist types:
35 # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time
36 #
37 # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes.
38 #
39 # +IO+ and +StringIO+ objects are encoded and placed in <data> elements; other objects are <tt>Marshal.dump</tt>'ed unless they implement +to_plist_node+.
40 #
41 # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment.
42 def self.dump(obj, envelope = true)
43 output = plist_node(obj)
44
45 output = wrap(output) if envelope
46
47 return output
48 end
49
50 # Writes the serialized object's plist to the specified filename.
51 def self.save_plist(obj, filename)
52 File.open(filename, 'wb') do |f|
53 f.write(obj.to_plist)
54 end
55 end
56
57 private
58 def self.plist_node(element)
59 output = ''
60
61 if element.respond_to? :to_plist_node
62 output << element.to_plist_node
63 else
64 case element
65 when Array
66 if element.empty?
67 output << "<array/>\n"
68 else
69 output << tag('array') {
70 element.collect {|e| plist_node(e)}
71 }
72 end
73 when Hash
74 if element.empty?
75 output << "<dict/>\n"
76 else
77 inner_tags = []
78
79 element.keys.sort.each do |k|
80 v = element[k]
81 inner_tags << tag('key', CGI::escapeHTML(k.to_s))
82 inner_tags << plist_node(v)
83 end
84
85 output << tag('dict') {
86 inner_tags
87 }
88 end
89 when true, false
90 output << "<#{element}/>\n"
91 when Time
92 output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ'))
93 when Date # also catches DateTime
94 output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ'))
95 when String, Symbol, Fixnum, Bignum, Integer, Float
96 output << tag(element_type(element), CGI::escapeHTML(element.to_s))
97 when IO, StringIO
98 element.rewind
99 contents = element.read
100 # note that apple plists are wrapped at a different length then
101 # what ruby's base64 wraps by default.
102 # I used #encode64 instead of #b64encode (which allows a length arg)
103 # because b64encode is b0rked and ignores the length arg.
104 data = "\n"
105 Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" }
106 output << tag('data', data)
107 else
108 output << comment( 'The <data> element below contains a Ruby object which has been serialized with Marshal.dump.' )
109 data = "\n"
110 Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" }
111 output << tag('data', data )
112 end
113 end
114
115 return output
116 end
117
118 def self.comment(content)
119 return "<!-- #{content} -->\n"
120 end
121
122 def self.tag(type, contents = '', &block)
123 out = nil
124
125 if block_given?
126 out = IndentedString.new
127 out << "<#{type}>"
128 out.raise_indent
129
130 out << block.call
131
132 out.lower_indent
133 out << "</#{type}>"
134 else
135 out = "<#{type}>#{contents.to_s}</#{type}>\n"
136 end
137
138 return out.to_s
139 end
140
141 def self.wrap(contents)
142 output = ''
143
144 output << '<?xml version="1.0" encoding="UTF-8"?>' + "\n"
145 output << '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' + "\n"
146 output << '<plist version="1.0">' + "\n"
147
148 output << contents
149
150 output << '</plist>' + "\n"
151
152 return output
153 end
154
155 def self.element_type(item)
156 return case item
157 when String, Symbol; 'string'
158 when Fixnum, Bignum, Integer; 'integer'
159 when Float; 'real'
160 else
161 raise "Don't know about this data type... something must be wrong!"
162 end
163 end
164
165 private
166
167 class IndentedString #:nodoc:
168 attr_accessor :indent_string
169
170 @@indent_level = 0
171
172 def initialize(str = "\t")
173 @indent_string = str
174 @contents = ''
175 end
176
177 def to_s
178 return @contents
179 end
180
181 def raise_indent
182 @@indent_level += 1
183 end
184
185 def lower_indent
186 @@indent_level -= 1 if @@indent_level > 0
187 end
188
189 def <<(val)
190 if val.is_a? Array
191 val.each do |f|
192 self << f
193 end
194 else
195 # if it's already indented, don't bother indenting further
196 unless val =~ /\A#{@indent_string}/
197 indent = @indent_string * @@indent_level
198
199 @contents << val.gsub(/^/, indent)
200 else
201 @contents << val
202 end
203
204 # it already has a newline, don't add another
205 @contents << "\n" unless val =~ /\n$/
206 end
207 end
208 end
209 end
210 end
211
212 # we need to add this so sorting hash keys works properly
213 class Symbol #:nodoc:
214 def <=> (other)
215 self.to_s <=> other.to_s
216 end
217 end
218
219 class Array #:nodoc:
220 include Plist::Emit
221 end
222
223 class Hash #:nodoc:
224 include Plist::Emit
225 end
226
227 # $Id: generator.rb 1781 2006-10-16 01:01:35Z luke $
0 #--###########################################################
1 # Copyright 2006, Ben Bleything <ben@bleything.net> and #
2 # Patrick May <patrick@hexane.org> #
3 # #
4 # Distributed under the MIT license. #
5 ##############################################################
6 #++
7 # Plist parses Mac OS X xml property list files into ruby data structures.
8 #
9 # === Load a plist file
10 # This is the main point of the library:
11 #
12 # r = Plist::parse_xml( filename_or_xml )
13 module Plist
14 # Note that I don't use these two elements much:
15 #
16 # + Date elements are returned as DateTime objects.
17 # + Data elements are implemented as Tempfiles
18 #
19 # Plist::parse_xml will blow up if it encounters a data element.
20 # If you encounter such an error, or if you have a Date element which
21 # can't be parsed into a Time object, please send your plist file to
22 # plist@hexane.org so that I can implement the proper support.
23 def Plist::parse_xml( filename_or_xml )
24 listener = Listener.new
25 #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener)
26 parser = StreamParser.new(filename_or_xml, listener)
27 parser.parse
28 listener.result
29 end
30
31 class Listener
32 #include REXML::StreamListener
33
34 attr_accessor :result, :open
35
36 def initialize
37 @result = nil
38 @open = Array.new
39 end
40
41
42 def tag_start(name, attributes)
43 @open.push PTag::mappings[name].new
44 end
45
46 def text( contents )
47 @open.last.text = contents if @open.last
48 end
49
50 def tag_end(name)
51 last = @open.pop
52 if @open.empty?
53 @result = last.to_ruby
54 else
55 @open.last.children.push last
56 end
57 end
58 end
59
60 class StreamParser
61 def initialize( filename_or_xml, listener )
62 @filename_or_xml = filename_or_xml
63 @listener = listener
64 end
65
66 TEXT = /([^<]+)/
67 XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um
68 DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
69 COMMENT_START = /\A<!--/u
70 COMMENT_END = /.*?-->/um
71
72 def parse
73 plist_tags = PTag::mappings.keys.join('|')
74 start_tag = /<(#{plist_tags})([^>]*)>/i
75 end_tag = /<\/(#{plist_tags})[^>]*>/i
76
77 require 'strscan'
78
79 contents = (
80 if (File.exists? @filename_or_xml)
81 File.open(@filename_or_xml) {|f| f.read}
82 else
83 @filename_or_xml
84 end
85 )
86
87 @scanner = StringScanner.new( contents )
88 until @scanner.eos?
89 if @scanner.scan(COMMENT_START)
90 @scanner.scan(COMMENT_END)
91 elsif @scanner.scan(XMLDECL_PATTERN)
92 elsif @scanner.scan(DOCTYPE_PATTERN)
93 elsif @scanner.scan(start_tag)
94 @listener.tag_start(@scanner[1], nil)
95 if (@scanner[2] =~ /\/$/)
96 @listener.tag_end(@scanner[1])
97 end
98 elsif @scanner.scan(TEXT)
99 @listener.text(@scanner[1])
100 elsif @scanner.scan(end_tag)
101 @listener.tag_end(@scanner[1])
102 else
103 raise "Unimplemented element"
104 end
105 end
106 end
107 end
108
109 class PTag
110 @@mappings = { }
111 def PTag::mappings
112 @@mappings
113 end
114
115 def PTag::inherited( sub_class )
116 key = sub_class.to_s.downcase
117 key.gsub!(/^plist::/, '' )
118 key.gsub!(/^p/, '') unless key == "plist"
119
120 @@mappings[key] = sub_class
121 end
122
123 attr_accessor :text, :children
124 def initialize
125 @children = Array.new
126 end
127
128 def to_ruby
129 raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}"
130 end
131 end
132
133 class PList < PTag
134 def to_ruby
135 children.first.to_ruby if children.first
136 end
137 end
138
139 class PDict < PTag
140 def to_ruby
141 dict = Hash.new
142 key = nil
143
144 children.each do |c|
145 if key.nil?
146 key = c.to_ruby
147 else
148 dict[key] = c.to_ruby
149 key = nil
150 end
151 end
152
153 dict
154 end
155 end
156
157 class PKey < PTag
158 def to_ruby
159 CGI::unescapeHTML(text || '')
160 end
161 end
162
163 class PString < PTag
164 def to_ruby
165 CGI::unescapeHTML(text || '')
166 end
167 end
168
169 class PArray < PTag
170 def to_ruby
171 children.collect do |c|
172 c.to_ruby
173 end
174 end
175 end
176
177 class PInteger < PTag
178 def to_ruby
179 text.to_i
180 end
181 end
182
183 class PTrue < PTag
184 def to_ruby
185 true
186 end
187 end
188
189 class PFalse < PTag
190 def to_ruby
191 false
192 end
193 end
194
195 class PReal < PTag
196 def to_ruby
197 text.to_f
198 end
199 end
200
201 require 'date'
202 class PDate < PTag
203 def to_ruby
204 DateTime.parse(text)
205 end
206 end
207
208 require 'base64'
209 class PData < PTag
210 def to_ruby
211 data = Base64.decode64(text.gsub(/\s+/, ''))
212
213 begin
214 return Marshal.load(data)
215 rescue Exception => e
216 io = StringIO.new
217 io.write data
218 io.rewind
219 return io
220 end
221 end
222 end
223 end
224
225 # $Id: parser.rb 1781 2006-10-16 01:01:35Z luke $
0 #--
1 ##############################################################
2 # Copyright 2006, Ben Bleything <ben@bleything.net> and #
3 # Patrick May <patrick@hexane.org> #
4 # #
5 # Distributed under the MIT license. #
6 ##############################################################
7 #++
8 # = Plist
9 #
10 # This is the main file for plist. Everything interesting happens in Plist and Plist::Emit.
11
12 require 'base64'
13 require 'cgi'
14 require 'stringio'
15
16 require 'facter/util/plist/generator'
17 require 'facter/util/plist/parser'
18
19 module Plist
20 VERSION = '3.0.0'
21 end
22
23 # $Id: plist.rb 1781 2006-10-16 01:01:35Z luke $