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