
1: # -*- mode: ruby; ruby-indent-level: 4; tab-width: 4 -*- vim: sw=4 ts=4 2: # $Id: yaml.rb 13940 2007-11-15 17:54:32Z why $ 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: oid = 388: case oid when Fixnum, NilClass; oid 389: else oid = "#{oid.object_id}-#{oid.hash}" 390: end 391: out.emit( oid, &e ) 392: end 393: 394: end 395: 396: require 'yaml/rubytypes' 397: require 'yaml/types' 398: 399: module Kernel 400: # 401: # ryan:: You know how Kernel.p is a really convenient way to dump ruby 402: # structures? The only downside is that it's not as legible as 403: # YAML. 404: # 405: # _why:: (listening) 406: # 407: # ryan:: I know you don't want to urinate all over your users' namespaces. 408: # But, on the other hand, convenience of dumping for debugging is, 409: # IMO, a big YAML use case. 410: # 411: # _why:: Go nuts! Have a pony parade! 412: # 413: # ryan:: Either way, I certainly will have a pony parade. 414: # 415: 416: # Prints any supplied _objects_ out in YAML. Intended as 417: # a variation on +Kernel::p+. 418: # 419: # S = Struct.new(:name, :state) 420: # s = S['dave', 'TX'] 421: # y s 422: # 423: # _produces:_ 424: # 425: # --- !ruby/struct:S 426: # name: dave 427: # state: TX 428: # 429: def y( object, *objects ) 430: objects.unshift object 431: puts( if objects.length == 1 432: YAML::dump( *objects ) 433: else 434: YAML::dump_stream( *objects ) 435: end ) 436: end 437: private :y 438: end 439: 440: