1 | # -*- mode: ruby; ruby-indent-level: 4; tab-width: 4 -*- vim: sw=4 ts=4
|
---|
2 | # $Id: yaml.rb 11708 2007-02-12 23:01:19Z shyouhei $
|
---|
3 | #
|
---|
4 | # = yaml.rb: top-level module with methods for loading and parsing YAML documents
|
---|
5 | #
|
---|
6 | # Author:: why the lucky stiff
|
---|
7 | #
|
---|
8 |
|
---|
9 | require 'stringio'
|
---|
10 | require 'yaml/error'
|
---|
11 | require 'yaml/syck'
|
---|
12 | require 'yaml/tag'
|
---|
13 | require 'yaml/stream'
|
---|
14 | require 'yaml/constants'
|
---|
15 |
|
---|
16 | # == YAML
|
---|
17 | #
|
---|
18 | # YAML(tm) (rhymes with 'camel') is a
|
---|
19 | # straightforward machine parsable data serialization format designed for
|
---|
20 | # human readability and interaction with scripting languages such as Perl
|
---|
21 | # and Python. YAML is optimized for data serialization, formatted
|
---|
22 | # dumping, configuration files, log files, Internet messaging and
|
---|
23 | # filtering. This specification describes the YAML information model and
|
---|
24 | # serialization format. Together with the Unicode standard for characters, it
|
---|
25 | # provides all the information necessary to understand YAML Version 1.0
|
---|
26 | # and construct computer programs to process it.
|
---|
27 | #
|
---|
28 | # See http://yaml.org/ for more information. For a quick tutorial, please
|
---|
29 | # visit YAML In Five Minutes (http://yaml.kwiki.org/?YamlInFiveMinutes).
|
---|
30 | #
|
---|
31 | # == About This Library
|
---|
32 | #
|
---|
33 | # The YAML 1.0 specification outlines four stages of YAML loading and dumping.
|
---|
34 | # This library honors all four of those stages, although data is really only
|
---|
35 | # available to you in three stages.
|
---|
36 | #
|
---|
37 | # The four stages are: native, representation, serialization, and presentation.
|
---|
38 | #
|
---|
39 | # The native stage refers to data which has been loaded completely into Ruby's
|
---|
40 | # own types. (See +YAML::load+.)
|
---|
41 | #
|
---|
42 | # The representation stage means data which has been composed into
|
---|
43 | # +YAML::BaseNode+ objects. In this stage, the document is available as a
|
---|
44 | # tree of node objects. You can perform YPath queries and transformations
|
---|
45 | # at this level. (See +YAML::parse+.)
|
---|
46 | #
|
---|
47 | # The serialization stage happens inside the parser. The YAML parser used in
|
---|
48 | # Ruby is called Syck. Serialized nodes are available in the extension as
|
---|
49 | # SyckNode structs.
|
---|
50 | #
|
---|
51 | # The presentation stage is the YAML document itself. This is accessible
|
---|
52 | # to you as a string. (See +YAML::dump+.)
|
---|
53 | #
|
---|
54 | # For more information about the various information models, see Chapter
|
---|
55 | # 3 of the YAML 1.0 Specification (http://yaml.org/spec/#id2491269).
|
---|
56 | #
|
---|
57 | # The YAML module provides quick access to the most common loading (YAML::load)
|
---|
58 | # and dumping (YAML::dump) tasks. This module also provides an API for registering
|
---|
59 | # global types (YAML::add_domain_type).
|
---|
60 | #
|
---|
61 | # == Example
|
---|
62 | #
|
---|
63 | # A simple round-trip (load and dump) of an object.
|
---|
64 | #
|
---|
65 | # require "yaml"
|
---|
66 | #
|
---|
67 | # test_obj = ["dogs", "cats", "badgers"]
|
---|
68 | #
|
---|
69 | # yaml_obj = YAML::dump( test_obj )
|
---|
70 | # # -> ---
|
---|
71 | # - dogs
|
---|
72 | # - cats
|
---|
73 | # - badgers
|
---|
74 | # ruby_obj = YAML::load( yaml_obj )
|
---|
75 | # # => ["dogs", "cats", "badgers"]
|
---|
76 | # ruby_obj == test_obj
|
---|
77 | # # => true
|
---|
78 | #
|
---|
79 | # To register your custom types with the global resolver, use +add_domain_type+.
|
---|
80 | #
|
---|
81 | # YAML::add_domain_type( "your-site.com,2004", "widget" ) do |type, val|
|
---|
82 | # Widget.new( val )
|
---|
83 | # end
|
---|
84 | #
|
---|
85 | module YAML
|
---|
86 |
|
---|
87 | Resolver = YAML::Syck::Resolver
|
---|
88 | DefaultResolver = YAML::Syck::DefaultResolver
|
---|
89 | DefaultResolver.use_types_at( @@tagged_classes )
|
---|
90 | GenericResolver = YAML::Syck::GenericResolver
|
---|
91 | Parser = YAML::Syck::Parser
|
---|
92 | Emitter = YAML::Syck::Emitter
|
---|
93 |
|
---|
94 | # Returns a new default parser
|
---|
95 | def YAML.parser; Parser.new.set_resolver( YAML.resolver ); end
|
---|
96 | # Returns a new generic parser
|
---|
97 | def YAML.generic_parser; Parser.new.set_resolver( GenericResolver ); end
|
---|
98 | # Returns the default resolver
|
---|
99 | def YAML.resolver; DefaultResolver; end
|
---|
100 | # Returns a new default emitter
|
---|
101 | def YAML.emitter; Emitter.new.set_resolver( YAML.resolver ); end
|
---|
102 |
|
---|
103 | #
|
---|
104 | # Converts _obj_ to YAML and writes the YAML result to _io_.
|
---|
105 | #
|
---|
106 | # File.open( 'animals.yaml', 'w' ) do |out|
|
---|
107 | # YAML.dump( ['badger', 'elephant', 'tiger'], out )
|
---|
108 | # end
|
---|
109 | #
|
---|
110 | # If no _io_ is provided, a string containing the dumped YAML
|
---|
111 | # is returned.
|
---|
112 | #
|
---|
113 | # YAML.dump( :locked )
|
---|
114 | # #=> "--- :locked"
|
---|
115 | #
|
---|
116 | def YAML.dump( obj, io = nil )
|
---|
117 | obj.to_yaml( io || io2 = StringIO.new )
|
---|
118 | io || ( io2.rewind; io2.read )
|
---|
119 | end
|
---|
120 |
|
---|
121 | #
|
---|
122 | # Load a document from the current _io_ stream.
|
---|
123 | #
|
---|
124 | # File.open( 'animals.yaml' ) { |yf| YAML::load( yf ) }
|
---|
125 | # #=> ['badger', 'elephant', 'tiger']
|
---|
126 | #
|
---|
127 | # Can also load from a string.
|
---|
128 | #
|
---|
129 | # YAML.load( "--- :locked" )
|
---|
130 | # #=> :locked
|
---|
131 | #
|
---|
132 | def YAML.load( io )
|
---|
133 | yp = parser.load( io )
|
---|
134 | end
|
---|
135 |
|
---|
136 | #
|
---|
137 | # Load a document from the file located at _filepath_.
|
---|
138 | #
|
---|
139 | # YAML.load_file( 'animals.yaml' )
|
---|
140 | # #=> ['badger', 'elephant', 'tiger']
|
---|
141 | #
|
---|
142 | def YAML.load_file( filepath )
|
---|
143 | File.open( filepath ) do |f|
|
---|
144 | load( f )
|
---|
145 | end
|
---|
146 | end
|
---|
147 |
|
---|
148 | #
|
---|
149 | # Parse the first document from the current _io_ stream
|
---|
150 | #
|
---|
151 | # File.open( 'animals.yaml' ) { |yf| YAML::load( yf ) }
|
---|
152 | # #=> #<YAML::Syck::Node:0x82ccce0
|
---|
153 | # @kind=:seq,
|
---|
154 | # @value=
|
---|
155 | # [#<YAML::Syck::Node:0x82ccd94
|
---|
156 | # @kind=:scalar,
|
---|
157 | # @type_id="str",
|
---|
158 | # @value="badger">,
|
---|
159 | # #<YAML::Syck::Node:0x82ccd58
|
---|
160 | # @kind=:scalar,
|
---|
161 | # @type_id="str",
|
---|
162 | # @value="elephant">,
|
---|
163 | # #<YAML::Syck::Node:0x82ccd1c
|
---|
164 | # @kind=:scalar,
|
---|
165 | # @type_id="str",
|
---|
166 | # @value="tiger">]>
|
---|
167 | #
|
---|
168 | # Can also load from a string.
|
---|
169 | #
|
---|
170 | # YAML.parse( "--- :locked" )
|
---|
171 | # #=> #<YAML::Syck::Node:0x82edddc
|
---|
172 | # @type_id="tag:ruby.yaml.org,2002:sym",
|
---|
173 | # @value=":locked", @kind=:scalar>
|
---|
174 | #
|
---|
175 | def YAML.parse( io )
|
---|
176 | yp = generic_parser.load( io )
|
---|
177 | end
|
---|
178 |
|
---|
179 | #
|
---|
180 | # Parse a document from the file located at _filepath_.
|
---|
181 | #
|
---|
182 | # YAML.parse_file( 'animals.yaml' )
|
---|
183 | # #=> #<YAML::Syck::Node:0x82ccce0
|
---|
184 | # @kind=:seq,
|
---|
185 | # @value=
|
---|
186 | # [#<YAML::Syck::Node:0x82ccd94
|
---|
187 | # @kind=:scalar,
|
---|
188 | # @type_id="str",
|
---|
189 | # @value="badger">,
|
---|
190 | # #<YAML::Syck::Node:0x82ccd58
|
---|
191 | # @kind=:scalar,
|
---|
192 | # @type_id="str",
|
---|
193 | # @value="elephant">,
|
---|
194 | # #<YAML::Syck::Node:0x82ccd1c
|
---|
195 | # @kind=:scalar,
|
---|
196 | # @type_id="str",
|
---|
197 | # @value="tiger">]>
|
---|
198 | #
|
---|
199 | def YAML.parse_file( filepath )
|
---|
200 | File.open( filepath ) do |f|
|
---|
201 | parse( f )
|
---|
202 | end
|
---|
203 | end
|
---|
204 |
|
---|
205 | #
|
---|
206 | # Calls _block_ with each consecutive document in the YAML
|
---|
207 | # stream contained in _io_.
|
---|
208 | #
|
---|
209 | # File.open( 'many-docs.yaml' ) do |yf|
|
---|
210 | # YAML.each_document( yf ) do |ydoc|
|
---|
211 | # ## ydoc contains the single object
|
---|
212 | # ## from the YAML document
|
---|
213 | # end
|
---|
214 | # end
|
---|
215 | #
|
---|
216 | def YAML.each_document( io, &block )
|
---|
217 | yp = parser.load_documents( io, &block )
|
---|
218 | end
|
---|
219 |
|
---|
220 | #
|
---|
221 | # Calls _block_ with each consecutive document in the YAML
|
---|
222 | # stream contained in _io_.
|
---|
223 | #
|
---|
224 | # File.open( 'many-docs.yaml' ) do |yf|
|
---|
225 | # YAML.load_documents( yf ) do |ydoc|
|
---|
226 | # ## ydoc contains the single object
|
---|
227 | # ## from the YAML document
|
---|
228 | # end
|
---|
229 | # end
|
---|
230 | #
|
---|
231 | def YAML.load_documents( io, &doc_proc )
|
---|
232 | YAML.each_document( io, &doc_proc )
|
---|
233 | end
|
---|
234 |
|
---|
235 | #
|
---|
236 | # Calls _block_ with a tree of +YAML::BaseNodes+, one tree for
|
---|
237 | # each consecutive document in the YAML stream contained in _io_.
|
---|
238 | #
|
---|
239 | # File.open( 'many-docs.yaml' ) do |yf|
|
---|
240 | # YAML.each_node( yf ) do |ydoc|
|
---|
241 | # ## ydoc contains a tree of nodes
|
---|
242 | # ## from the YAML document
|
---|
243 | # end
|
---|
244 | # end
|
---|
245 | #
|
---|
246 | def YAML.each_node( io, &doc_proc )
|
---|
247 | yp = generic_parser.load_documents( io, &doc_proc )
|
---|
248 | end
|
---|
249 |
|
---|
250 | #
|
---|
251 | # Calls _block_ with a tree of +YAML::BaseNodes+, one tree for
|
---|
252 | # each consecutive document in the YAML stream contained in _io_.
|
---|
253 | #
|
---|
254 | # File.open( 'many-docs.yaml' ) do |yf|
|
---|
255 | # YAML.parse_documents( yf ) do |ydoc|
|
---|
256 | # ## ydoc contains a tree of nodes
|
---|
257 | # ## from the YAML document
|
---|
258 | # end
|
---|
259 | # end
|
---|
260 | #
|
---|
261 | def YAML.parse_documents( io, &doc_proc )
|
---|
262 | YAML.each_node( io, &doc_proc )
|
---|
263 | end
|
---|
264 |
|
---|
265 | #
|
---|
266 | # Loads all documents from the current _io_ stream,
|
---|
267 | # returning a +YAML::Stream+ object containing all
|
---|
268 | # loaded documents.
|
---|
269 | #
|
---|
270 | def YAML.load_stream( io )
|
---|
271 | d = nil
|
---|
272 | parser.load_documents( io ) do |doc|
|
---|
273 | d = YAML::Stream.new if not d
|
---|
274 | d.add( doc )
|
---|
275 | end
|
---|
276 | return d
|
---|
277 | end
|
---|
278 |
|
---|
279 | #
|
---|
280 | # Returns a YAML stream containing each of the items in +objs+,
|
---|
281 | # each having their own document.
|
---|
282 | #
|
---|
283 | # YAML.dump_stream( 0, [], {} )
|
---|
284 | # #=> --- 0
|
---|
285 | # --- []
|
---|
286 | # --- {}
|
---|
287 | #
|
---|
288 | def YAML.dump_stream( *objs )
|
---|
289 | d = YAML::Stream.new
|
---|
290 | objs.each do |doc|
|
---|
291 | d.add( doc )
|
---|
292 | end
|
---|
293 | d.emit
|
---|
294 | end
|
---|
295 |
|
---|
296 | #
|
---|
297 | # Add a global handler for a YAML domain type.
|
---|
298 | #
|
---|
299 | def YAML.add_domain_type( domain, type_tag, &transfer_proc )
|
---|
300 | resolver.add_type( "tag:#{ domain }:#{ type_tag }", transfer_proc )
|
---|
301 | end
|
---|
302 |
|
---|
303 | #
|
---|
304 | # Add a transfer method for a builtin type
|
---|
305 | #
|
---|
306 | def YAML.add_builtin_type( type_tag, &transfer_proc )
|
---|
307 | resolver.add_type( "tag:yaml.org,2002:#{ type_tag }", transfer_proc )
|
---|
308 | end
|
---|
309 |
|
---|
310 | #
|
---|
311 | # Add a transfer method for a builtin type
|
---|
312 | #
|
---|
313 | def YAML.add_ruby_type( type_tag, &transfer_proc )
|
---|
314 | resolver.add_type( "tag:ruby.yaml.org,2002:#{ type_tag }", transfer_proc )
|
---|
315 | end
|
---|
316 |
|
---|
317 | #
|
---|
318 | # Add a private document type
|
---|
319 | #
|
---|
320 | def YAML.add_private_type( type_re, &transfer_proc )
|
---|
321 | resolver.add_type( "x-private:" + type_re, transfer_proc )
|
---|
322 | end
|
---|
323 |
|
---|
324 | #
|
---|
325 | # Detect typing of a string
|
---|
326 | #
|
---|
327 | def YAML.detect_implicit( val )
|
---|
328 | resolver.detect_implicit( val )
|
---|
329 | end
|
---|
330 |
|
---|
331 | #
|
---|
332 | # Convert a type_id to a taguri
|
---|
333 | #
|
---|
334 | def YAML.tagurize( val )
|
---|
335 | resolver.tagurize( val )
|
---|
336 | end
|
---|
337 |
|
---|
338 | #
|
---|
339 | # Apply a transfer method to a Ruby object
|
---|
340 | #
|
---|
341 | def YAML.transfer( type_id, obj )
|
---|
342 | resolver.transfer( YAML.tagurize( type_id ), obj )
|
---|
343 | end
|
---|
344 |
|
---|
345 | #
|
---|
346 | # Apply any implicit a node may qualify for
|
---|
347 | #
|
---|
348 | def YAML.try_implicit( obj )
|
---|
349 | YAML.transfer( YAML.detect_implicit( obj ), obj )
|
---|
350 | end
|
---|
351 |
|
---|
352 | #
|
---|
353 | # Method to extract colon-seperated type and class, returning
|
---|
354 | # the type and the constant of the class
|
---|
355 | #
|
---|
356 | def YAML.read_type_class( type, obj_class )
|
---|
357 | scheme, domain, type, tclass = type.split( ':', 4 )
|
---|
358 | tclass.split( "::" ).each { |c| obj_class = obj_class.const_get( c ) } if tclass
|
---|
359 | return [ type, obj_class ]
|
---|
360 | end
|
---|
361 |
|
---|
362 | #
|
---|
363 | # Allocate blank object
|
---|
364 | #
|
---|
365 | def YAML.object_maker( obj_class, val )
|
---|
366 | if Hash === val
|
---|
367 | o = obj_class.allocate
|
---|
368 | val.each_pair { |k,v|
|
---|
369 | o.instance_variable_set("@#{k}", v)
|
---|
370 | }
|
---|
371 | o
|
---|
372 | else
|
---|
373 | raise YAML::Error, "Invalid object explicitly tagged !ruby/Object: " + val.inspect
|
---|
374 | end
|
---|
375 | end
|
---|
376 |
|
---|
377 | #
|
---|
378 | # Allocate an Emitter if needed
|
---|
379 | #
|
---|
380 | def YAML.quick_emit( oid, opts = {}, &e )
|
---|
381 | out =
|
---|
382 | if opts.is_a? YAML::Emitter
|
---|
383 | opts
|
---|
384 | else
|
---|
385 | emitter.reset( opts )
|
---|
386 | end
|
---|
387 | out.emit( oid, &e )
|
---|
388 | end
|
---|
389 |
|
---|
390 | end
|
---|
391 |
|
---|
392 | require 'yaml/rubytypes'
|
---|
393 | require 'yaml/types'
|
---|
394 |
|
---|
395 | module Kernel
|
---|
396 | #
|
---|
397 | # ryan:: You know how Kernel.p is a really convenient way to dump ruby
|
---|
398 | # structures? The only downside is that it's not as legible as
|
---|
399 | # YAML.
|
---|
400 | #
|
---|
401 | # _why:: (listening)
|
---|
402 | #
|
---|
403 | # ryan:: I know you don't want to urinate all over your users' namespaces.
|
---|
404 | # But, on the other hand, convenience of dumping for debugging is,
|
---|
405 | # IMO, a big YAML use case.
|
---|
406 | #
|
---|
407 | # _why:: Go nuts! Have a pony parade!
|
---|
408 | #
|
---|
409 | # ryan:: Either way, I certainly will have a pony parade.
|
---|
410 | #
|
---|
411 |
|
---|
412 | # Prints any supplied _objects_ out in YAML. Intended as
|
---|
413 | # a variation on +Kernel::p+.
|
---|
414 | #
|
---|
415 | # S = Struct.new(:name, :state)
|
---|
416 | # s = S['dave', 'TX']
|
---|
417 | # y s
|
---|
418 | #
|
---|
419 | # _produces:_
|
---|
420 | #
|
---|
421 | # --- !ruby/struct:S
|
---|
422 | # name: dave
|
---|
423 | # state: TX
|
---|
424 | #
|
---|
425 | def y( object, *objects )
|
---|
426 | objects.unshift object
|
---|
427 | puts( if objects.length == 1
|
---|
428 | YAML::dump( *objects )
|
---|
429 | else
|
---|
430 | YAML::dump_stream( *objects )
|
---|
431 | end )
|
---|
432 | end
|
---|
433 | private :y
|
---|
434 | end
|
---|
435 |
|
---|
436 |
|
---|