psychgus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/psychgus.rb ADDED
@@ -0,0 +1,693 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ #--
5
+ # This file is part of Psychgus.
6
+ # Copyright (c) 2017-2019 Jonathan Bradley Whited (@esotericpig)
7
+ #
8
+ # Psychgus is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Lesser General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # Psychgus is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License
19
+ # along with Psychgus. If not, see <http://www.gnu.org/licenses/>.
20
+ #++
21
+
22
+
23
+ require 'psych'
24
+
25
+ require 'psychgus/blueberry'
26
+ require 'psychgus/ext'
27
+ require 'psychgus/styled_document_stream'
28
+ require 'psychgus/styled_tree_builder'
29
+ require 'psychgus/styler'
30
+ require 'psychgus/super_sniffer'
31
+ require 'psychgus/version'
32
+
33
+ require 'psychgus/ext/core_ext'
34
+ require 'psychgus/ext/node_ext'
35
+ require 'psychgus/ext/yaml_tree_ext'
36
+
37
+ require 'psychgus/super_sniffer/parent'
38
+
39
+ ###
40
+ # Psychgus uses the core standard library {https://github.com/ruby/psych Psych} for working with YAML
41
+ # and extends it so that developers can easily style the YAML according to their needs.
42
+ # Thank you to the people that worked and continue to work hard on that project.
43
+ #
44
+ # The name comes from the well-styled character Gus from the TV show Psych.
45
+ #
46
+ # == Create a Styler
47
+ #
48
+ # First, we will create a {Styler}.
49
+ #
50
+ # All you need to do is add +include Psychgus::Styler+ to a class.
51
+ #
52
+ # Here is a complex {Styler} for the examples below:
53
+ # require 'psychgus'
54
+ #
55
+ # class BurgerStyler
56
+ # # Mix in methods needed for styling
57
+ # include Psychgus::Styler
58
+ #
59
+ # def initialize(sniffer=nil)
60
+ # if sniffer.nil?()
61
+ # @class_level = 0
62
+ # @class_position = 0
63
+ # else
64
+ # # For the Class Example
65
+ # @class_level = sniffer.level
66
+ # @class_position = sniffer.position
67
+ # end
68
+ # end
69
+ #
70
+ # # Style all nodes (Psych::Nodes::Node)
71
+ # def style(sniffer,node)
72
+ # # Remove "!ruby/object:..." for classes
73
+ # node.tag = nil if node.node_of?(:mapping,:scalar,:sequence)
74
+ #
75
+ # # This is another way to do the above
76
+ # #node.tag = nil if node.respond_to?(:tag=)
77
+ # end
78
+ #
79
+ # # Style aliases (Psych::Nodes::Alias)
80
+ # def style_alias(sniffer,node)
81
+ # end
82
+ #
83
+ # # Style maps (Psych::Nodes::Mapping)
84
+ # # - Hashes (key/value pairs)
85
+ # # - Example: "Burgers: Classic {}"
86
+ # def style_mapping(sniffer,node)
87
+ # parent = sniffer.parent
88
+ #
89
+ # if !parent.nil?()
90
+ # # BBQ
91
+ # node.style = Psychgus::MAPPING_FLOW if parent.node_of?(:scalar) &&
92
+ # parent.value.casecmp('BBQ') == 0
93
+ # end
94
+ # end
95
+ #
96
+ # # Style scalars (Psych::Nodes::Scalar)
97
+ # # - Any text (non-alias)
98
+ # def style_scalar(sniffer,node)
99
+ # parent = sniffer.parent
100
+ #
101
+ # # Single quote scalars that are not keys to a map
102
+ # node.style = Psychgus::SCALAR_SINGLE_QUOTED if !parent.nil?() &&
103
+ # parent.child_type != :key
104
+ #
105
+ # # Remove colon (change symbols into strings)
106
+ # node.value = node.value.sub(':','')
107
+ #
108
+ # # Change lettuce to spinach
109
+ # node.value = 'Spinach' if node.value.casecmp('Lettuce') == 0
110
+ #
111
+ # # Capitalize each word
112
+ # node.value = node.value.split(' ').map do |v|
113
+ # if v.casecmp('BBQ') == 0
114
+ # v.upcase()
115
+ # else
116
+ # v.capitalize()
117
+ # end
118
+ # end.join(' ')
119
+ # end
120
+ #
121
+ # # Style sequences (Psych::Nodes::Sequence)
122
+ # # - Arrays
123
+ # # - Example: "[Lettuce, Onions, Pickles, Tomatoes]"
124
+ # def style_sequence(sniffer,node)
125
+ # relative_level = (sniffer.level - @class_level) + 1
126
+ #
127
+ # node.style = Psychgus::SEQUENCE_FLOW if sniffer.level >= 4
128
+ #
129
+ # # Make "[Ketchup, Mustard]" a block for the Class Example
130
+ # node.style = Psychgus::SEQUENCE_BLOCK if relative_level == 7
131
+ # end
132
+ # end
133
+ #
134
+ # @example Hash example
135
+ # require 'psychgus'
136
+ #
137
+ # burgers = {
138
+ # :Burgers => {
139
+ # :Classic => {
140
+ # :Sauce => %w(Ketchup Mustard),
141
+ # :Cheese => 'American',
142
+ # :Bun => 'Sesame Seed'
143
+ # },
144
+ # :BBQ => {
145
+ # :Sauce => 'Honey BBQ',
146
+ # :Cheese => 'Cheddar',
147
+ # :Bun => 'Kaiser'
148
+ # },
149
+ # :Fancy => {
150
+ # :Sauce => 'Spicy Wasabi',
151
+ # :Cheese => 'Smoked Gouda',
152
+ # :Bun => 'Hawaiian'
153
+ # }
154
+ # },
155
+ # :Toppings => [
156
+ # 'Mushrooms',
157
+ # %w(Lettuce Onions Pickles Tomatoes),
158
+ # [%w(Ketchup Mustard), %w(Salt Pepper)]
159
+ # ]
160
+ # }
161
+ # burgers[:Favorite] = burgers[:Burgers][:BBQ] # Alias
162
+ #
163
+ # puts burgers.to_yaml(indent: 3,stylers: BurgerStyler.new,deref_aliases: true)
164
+ #
165
+ # # Output:
166
+ # # ---
167
+ # # Burgers:
168
+ # # Classic:
169
+ # # Sauce: ['Ketchup', 'Mustard']
170
+ # # Cheese: 'American'
171
+ # # Bun: 'Sesame Seed'
172
+ # # BBQ: {Sauce: 'Honey BBQ', Cheese: 'Cheddar', Bun: 'Kaiser'}
173
+ # # Fancy:
174
+ # # Sauce: 'Spicy Wasabi'
175
+ # # Cheese: 'Smoked Gouda'
176
+ # # Bun: 'Hawaiian'
177
+ # # Toppings:
178
+ # # - 'Mushrooms'
179
+ # # - ['Spinach', 'Onions', 'Pickles', 'Tomatoes']
180
+ # # - [['Ketchup', 'Mustard'], ['Salt', 'Pepper']]
181
+ # # Favorite:
182
+ # # Sauce: 'Honey BBQ'
183
+ # # Cheese: 'Cheddar'
184
+ # # Bun: 'Kaiser'
185
+ #
186
+ # @example Class example
187
+ # require 'psychgus'
188
+ #
189
+ # class Burger
190
+ # attr_accessor :bun
191
+ # attr_accessor :cheese
192
+ # attr_accessor :sauce
193
+ #
194
+ # def initialize(sauce,cheese,bun)
195
+ # @bun = bun
196
+ # @cheese = cheese
197
+ # @sauce = sauce
198
+ # end
199
+ #
200
+ # # You can still use Psych's encode_with(), no problem
201
+ # #def encode_with(coder)
202
+ # # coder['Bun'] = @bun
203
+ # # coder['Cheese'] = @cheese
204
+ # # coder['Sauce'] = @sauce
205
+ # #end
206
+ # end
207
+ #
208
+ # class Burgers
209
+ # include Psychgus::Blueberry
210
+ #
211
+ # attr_accessor :burgers
212
+ # attr_accessor :toppings
213
+ # attr_accessor :favorite
214
+ #
215
+ # def initialize()
216
+ # @burgers = {
217
+ # 'Classic' => Burger.new(['Ketchup','Mustard'],'American','Sesame Seed'),
218
+ # 'BBQ' => Burger.new('Honey BBQ','Cheddar','Kaiser'),
219
+ # 'Fancy' => Burger.new('Spicy Wasabi','Smoked Gouda','Hawaiian')
220
+ # }
221
+ #
222
+ # @toppings = [
223
+ # 'Mushrooms',
224
+ # %w(Lettuce Onions Pickles Tomatoes),
225
+ # [%w(Ketchup Mustard),%w(Salt Pepper)]
226
+ # ]
227
+ #
228
+ # @favorite = @burgers['BBQ'] # Alias
229
+ # end
230
+ #
231
+ # def psychgus_stylers(sniffer)
232
+ # return BurgerStyler.new(sniffer)
233
+ # end
234
+ #
235
+ # # You can still use Psych's encode_with(), no problem
236
+ # #def encode_with(coder)
237
+ # # coder['Burgers'] = @burgers
238
+ # # coder['Toppings'] = @toppings
239
+ # # coder['Favorite'] = @favorite
240
+ # #end
241
+ # end
242
+ #
243
+ # burgers = Burgers.new
244
+ # puts burgers.to_yaml(indent: 3,deref_aliases: true)
245
+ #
246
+ # # Output:
247
+ # # ---
248
+ # # Burgers:
249
+ # # Classic:
250
+ # # Bun: 'Sesame Seed'
251
+ # # Cheese: 'American'
252
+ # # Sauce:
253
+ # # - 'Ketchup'
254
+ # # - 'Mustard'
255
+ # # BBQ: {Bun: 'Kaiser', Cheese: 'Cheddar', Sauce: 'Honey BBQ'}
256
+ # # Fancy:
257
+ # # Bun: 'Hawaiian'
258
+ # # Cheese: 'Smoked Gouda'
259
+ # # Sauce: 'Spicy Wasabi'
260
+ # # Toppings:
261
+ # # - 'Mushrooms'
262
+ # # - ['Spinach', 'Onions', 'Pickles', 'Tomatoes']
263
+ # # - [['Ketchup', 'Mustard'], ['Salt', 'Pepper']]
264
+ # # Favorite:
265
+ # # Bun: 'Kaiser'
266
+ # # Cheese: 'Cheddar'
267
+ # # Sauce: 'Honey BBQ'
268
+ #
269
+ # @example Emitting / Parsing examples
270
+ # styler = BurgerStyler.new()
271
+ # options = {:indentation=>3,:stylers=>styler,:deref_aliases=>true}
272
+ # yaml = burgers.to_yaml(options)
273
+ #
274
+ # # High-level emitting
275
+ # Psychgus.dump(burgers,options)
276
+ # Psychgus.dump_file('burgers.yaml',burgers,options)
277
+ # burgers.to_yaml(options)
278
+ #
279
+ # # High-level parsing
280
+ # # - Because to_ruby() will be called, just use Psych:
281
+ # # - load(), load_file(), load_stream(), safe_load()
282
+ #
283
+ # # Mid-level emitting
284
+ # stream = Psychgus.parse_stream(yaml,stylers: styler,deref_aliases: true)
285
+ #
286
+ # stream.to_yaml()
287
+ #
288
+ # # Mid-level parsing
289
+ # Psychgus.parse(yaml,stylers: styler,deref_aliases: true)
290
+ # Psychgus.parse_file('burgers.yaml',stylers: styler,deref_aliases: true)
291
+ # Psychgus.parse_stream(yaml,stylers: styler,deref_aliases: true)
292
+ #
293
+ # # Low-level emitting
294
+ # tree_builder = Psychgus::StyledTreeBuilder.new(styler,deref_aliases: true)
295
+ # visitor = Psych::Visitors::YAMLTree.create(options,tree_builder)
296
+ #
297
+ # visitor << burgers
298
+ # visitor.tree.to_yaml
299
+ #
300
+ # # Low-level parsing
301
+ # parser = Psychgus.parser(stylers: styler,deref_aliases: true)
302
+ #
303
+ # parser.parse(yaml)
304
+ # parser.handler
305
+ # parser.handler.root
306
+ #
307
+ # @author Jonathan Bradley Whited (@esotericpig)
308
+ # @since 1.0.0
309
+ ###
310
+ module Psychgus
311
+ NODE_CLASS_ALIASES = {:Doc => :Document,:Map => :Mapping,:Seq => :Sequence}
312
+ OPTIONS_ALIASES = {:canon => :canonical,:indent => :indentation}
313
+
314
+ # Get a Class (constant) from Psych::Nodes.
315
+ #
316
+ # Some +name+s have aliases:
317
+ # :doc => :document
318
+ # :map => :mapping
319
+ # :seq => :sequence
320
+ #
321
+ # @param name [Symbol,String] the name of the class from Psych::Nodes
322
+ #
323
+ # @return [Class] a class from Psych::Nodes
324
+ #
325
+ # @see Psych::Nodes
326
+ # @see NODE_CLASS_ALIASES
327
+ def self.node_class(name)
328
+ name = name.to_sym().capitalize()
329
+
330
+ name_alias = NODE_CLASS_ALIASES[name]
331
+ name = name_alias unless name_alias.nil?()
332
+
333
+ return Psych::Nodes.const_get(name)
334
+ end
335
+
336
+ # Get a constant from a Psych::Nodes class (using {.node_class}).
337
+ #
338
+ # @param class_name [Symbol,String] the name of the class to get using {.node_class}
339
+ # @param const_name [Symbol,String] the constant to get from the class
340
+ # @param lenient [true,false] if true, will return 0 if not const_defined?(), else raise an error
341
+ #
342
+ # @return [Integer,Object] the constant value from the class (usually an int)
343
+ #
344
+ # @see .node_class
345
+ def self.node_const(class_name,const_name,lenient=true)
346
+ node_class = node_class(class_name)
347
+ const_name = const_name.to_sym().upcase()
348
+
349
+ return 0 if lenient && !node_class.const_defined?(const_name,true)
350
+ return node_class.const_get(const_name,true)
351
+ end
352
+
353
+ MAPPING_ANY = node_const(:mapping,:any)
354
+ MAPPING_BLOCK = node_const(:mapping,:block)
355
+ MAPPING_FLOW = node_const(:mapping,:flow)
356
+ MAP_ANY = MAPPING_ANY
357
+ MAP_BLOCK = MAPPING_BLOCK
358
+ MAP_FLOW = MAPPING_FLOW
359
+
360
+ SCALAR_ANY = node_const(:scalar,:any)
361
+ SCALAR_PLAIN = node_const(:scalar,:plain)
362
+ SCALAR_SINGLE_QUOTED = node_const(:scalar,:single_quoted)
363
+ SCALAR_DOUBLE_QUOTED = node_const(:scalar,:double_quoted)
364
+ SCALAR_LITERAL = node_const(:scalar,:literal)
365
+ SCALAR_FOLDED = node_const(:scalar,:folded)
366
+
367
+ SEQUENCE_ANY = node_const(:sequence,:any)
368
+ SEQUENCE_BLOCK = node_const(:sequence,:block)
369
+ SEQUENCE_FLOW = node_const(:sequence,:flow)
370
+ SEQ_ANY = SEQUENCE_ANY
371
+ SEQ_BLOCK = SEQUENCE_BLOCK
372
+ SEQ_FLOW = SEQUENCE_FLOW
373
+
374
+ STREAM_ANY = node_const(:stream,:any)
375
+ STREAM_UTF8 = node_const(:stream,:utf8)
376
+ STREAM_UTF16LE = node_const(:stream,:utf16le)
377
+ STREAM_UTF16BE = node_const(:stream,:utf16be)
378
+
379
+ # Convert +object+ to YAML and dump to +io+.
380
+ #
381
+ # +object+, +io+, and +options+ are used like in Psych.dump so can be a drop-in replacement for Psych.
382
+ #
383
+ # @param object [Object] the Object to convert to YAML and dump
384
+ # @param io [nil,IO,Hash] the IO to dump the YAML to or the +options+ Hash; if nil, will use StringIO
385
+ # @param options [Hash] the options (or keyword args) to use; see {.dump_stream}
386
+ #
387
+ # @return [String,Object] the result of converting +object+ to YAML using the params
388
+ #
389
+ # @see .dump_stream
390
+ # @see Psych.dump_stream
391
+ def self.dump(object,io=nil,**options)
392
+ return dump_stream(object,io: io,**options)
393
+ end
394
+
395
+ # Convert +objects+ to YAML and dump to a file.
396
+ #
397
+ # @example
398
+ # Psychgus.dump_file('my_dir/my_file.yaml',my_object1,my_object2,mode: 'w:UTF-16',
399
+ # stylers: MyStyler.new())
400
+ # Psychgus.dump_file('my_file.yaml',my_object,stylers: [MyStyler1.new(),MyStyler2.new()])
401
+ #
402
+ # @param filename [String] the name of the file (and path) to dump to
403
+ # @param objects [Object,Array<Object>] the Object(s) to convert to YAML and dump
404
+ # @param mode [String,Integer] the IO open mode to use; examples:
405
+ # [+'w:UTF-8'+] create a new file or truncate an existing file
406
+ # and use UTF-8 encoding;
407
+ # [+'a:UTF-16'+] create a new file or append to an existing file
408
+ # and use UTF-16 encoding
409
+ # @param perm [Integer] the permission bits to use (platform dependent)
410
+ # @param opt [Symbol] the option(s) to use, more readable alternative to +mode+;
411
+ # examples: +:textmode+, +:autoclose+
412
+ # @param options [Hash] the options (or keyword args) to use; see {.dump_stream}
413
+ #
414
+ # @see .dump_stream
415
+ # @see File.open
416
+ # @see IO.new
417
+ # @see https://ruby-doc.org/core/IO.html#method-c-new
418
+ def self.dump_file(filename,*objects,mode: 'w',perm: nil,opt: nil,**options)
419
+ File.open(filename,mode,perm,opt) do |file|
420
+ file.write(dump_stream(*objects,**options))
421
+ end
422
+ end
423
+
424
+ # Convert +objects+ to YAML and dump to +io+.
425
+ #
426
+ # +io+ and +options+ are used like in Psych.dump so can be a drop-in replacement for Psych.
427
+ #
428
+ # @param objects [Object,Array<Object>] the Object(s) to convert to YAML and dump
429
+ # @param io [nil,IO,Hash] the IO to dump the YAML to or the +options+ Hash; if nil, will use StringIO
430
+ # @param stylers [nil,Styler,Array<Styler>] the Styler(s) to use when converting to YAML
431
+ # @param deref_aliases [true,false] whether to dereference aliases; output the actual value
432
+ # instead of the alias
433
+ # @param options [Hash] the options (or keyword args) to use when converting to YAML:
434
+ # [+:indent+] Alias for +:indentation+. +:indentation+ will override this.
435
+ # [+:indentation+] Default: +2+.
436
+ # Number of space characters used to indent.
437
+ # Acceptable value should be in +0..9+ range, else ignored.
438
+ # [+:line_width+] Default: +0+ (meaning "wrap at 81").
439
+ # Max character to wrap line at.
440
+ # [+:canon+] Alias for +:canonical+. +:canonical+ will override this.
441
+ # [+:canonical+] Default: +false+.
442
+ # Write "canonical" YAML form (very verbose, yet strictly formal).
443
+ # [+:header+] Default: +false+.
444
+ # Write +%YAML [version]+ at the beginning of document.
445
+ #
446
+ # @return [String,Object] the result of converting +object+ to YAML using the params
447
+ #
448
+ # @see Psych.dump_stream
449
+ # @see OPTIONS_ALIASES
450
+ def self.dump_stream(*objects,io: nil,stylers: nil,deref_aliases: false,**options)
451
+ if Hash === io
452
+ options = io
453
+ io = nil
454
+ end
455
+
456
+ if !options.nil?()
457
+ OPTIONS_ALIASES.each do |option_alias,option|
458
+ if options.key?(option_alias) && !options.key?(option)
459
+ options[option] = options[option_alias]
460
+ end
461
+ end
462
+ end
463
+
464
+ visitor = Psych::Visitors::YAMLTree.create(options,StyledTreeBuilder.new(*stylers,
465
+ deref_aliases: deref_aliases))
466
+
467
+ objects.each do |object|
468
+ visitor << object
469
+ end
470
+
471
+ return visitor.tree.yaml(io,options)
472
+ end
473
+
474
+ # Parse +yaml+ into a Psych::Nodes::Document.
475
+ #
476
+ # If you're just going to call to_ruby(), then using this method is unnecessary,
477
+ # and the styler(s) will do nothing for you.
478
+ #
479
+ # @param yaml [String] the YAML to parse
480
+ # @param kargs [Hash] the keyword args to use; see {.parse_stream}
481
+ #
482
+ # @return [Psych::Nodes::Document] the parsed Document node
483
+ #
484
+ # @see .parse_stream
485
+ # @see Psych.parse
486
+ # @see Psych::Nodes::Document
487
+ def self.parse(yaml,**kargs)
488
+ parse_stream(yaml,**kargs) do |node|
489
+ return node
490
+ end
491
+
492
+ return false
493
+ end
494
+
495
+ # Parse a YAML file into a Psych::Nodes::Document.
496
+ #
497
+ # If you're just going to call to_ruby(), then using this method is unnecessary,
498
+ # and the styler(s) will do nothing for you.
499
+ #
500
+ # @param filename [String] the name of the YAML file (and path) to parse
501
+ # @param fallback [Object] the return value when nothing is parsed
502
+ # @param mode [String,Integer] the IO open mode to use; example: +'r:BOM|UTF-8'+
503
+ # @param kargs [Hash] the keyword args to use; see {.parse_stream}
504
+ #
505
+ # @return [Psych::Nodes::Document] the parsed Document node
506
+ #
507
+ # @see .parse_stream
508
+ # @see Psych.parse_file
509
+ # @see Psych::Nodes::Document
510
+ # @see File.open
511
+ # @see IO.new
512
+ def self.parse_file(filename,fallback: false,mode: 'r:BOM|UTF-8',**kargs)
513
+ result = File.open(filename,mode) do |file|
514
+ parse(file,filename: filename,**kargs)
515
+ end
516
+
517
+ return result || fallback
518
+ end
519
+
520
+ # Parse +yaml+ into a Psych::Nodes::Stream for one document or for multiple documents in one YAML.
521
+ #
522
+ # If you're just going to call to_ruby(), then using this method is unnecessary,
523
+ # and the styler(s) will do nothing for you.
524
+ #
525
+ # @example
526
+ # burgers = <<EOY
527
+ # ---
528
+ # Burgers:
529
+ # Classic:
530
+ # BBQ: {Sauce: Honey BBQ, Cheese: Cheddar, Bun: Kaiser}
531
+ # ---
532
+ # Toppings:
533
+ # - [Mushrooms, Mustard]
534
+ # - [Salt, Pepper, Pickles]
535
+ # ---
536
+ # `Invalid`
537
+ # EOY
538
+ #
539
+ # i = 0
540
+ #
541
+ # begin
542
+ # Psychgus.parse_stream(burgers,filename: 'burgers.yaml') do |document|
543
+ # puts "Document ##{i += 1}"
544
+ # puts document.to_ruby
545
+ # end
546
+ # rescue Psych::SyntaxError => err
547
+ # puts "File: #{err.file}"
548
+ # end
549
+ #
550
+ # # Output:
551
+ # # Document #1
552
+ # # {"Burgers"=>{"Classic"=>{"BBQ"=>{"Sauce"=>"Honey BBQ", "Cheese"=>"Cheddar", "Bun"=>"Kaiser"}}}}
553
+ # # Document #2
554
+ # # {"Toppings"=>[["Mushrooms", "Mustard"], ["Salt", "Pepper", "Pickles"]]}
555
+ # # File: burgers.yaml
556
+ #
557
+ # @param yaml [String] the YAML to parse
558
+ # @param filename [String] the filename to pass as +file+ to the Error potentially raised
559
+ # @param stylers [nil,Styler,Array<Styler>] the Styler(s) to use when parsing the YAML
560
+ # @param deref_aliases [true,false] whether to dereference aliases; output the actual value
561
+ # instead of the alias
562
+ # @param block [Proc] an optional block for parsing multiple documents
563
+ #
564
+ # @return [Psych::Nodes::Stream] the parsed Stream node
565
+ #
566
+ # @see StyledDocumentStream
567
+ # @see Psych.parse_stream
568
+ # @see Psych::Nodes::Stream
569
+ # @see Psych::SyntaxError
570
+ def self.parse_stream(yaml,filename: nil,stylers: nil,deref_aliases: false,**options,&block)
571
+ if block_given?()
572
+ parser = Psych::Parser.new(StyledDocumentStream.new(*stylers,deref_aliases: deref_aliases,**options,
573
+ &block))
574
+
575
+ return parser.parse(yaml,filename)
576
+ else
577
+ parser = self.parser(stylers: stylers,deref_aliases: deref_aliases,**options)
578
+ parser.parse(yaml,filename)
579
+
580
+ return parser.handler.root
581
+ end
582
+ end
583
+
584
+ # Create a new styled Psych::Parser for parsing YAML.
585
+ #
586
+ # @example
587
+ # class CoffeeStyler
588
+ # include Psychgus::Styler
589
+ #
590
+ # def style_sequence(sniffer,node)
591
+ # node.style = Psychgus::SEQUENCE_FLOW
592
+ # end
593
+ # end
594
+ #
595
+ # coffee = <<EOY
596
+ # Coffee:
597
+ # Roast:
598
+ # - Light
599
+ # - Medium
600
+ # - Dark
601
+ # Style:
602
+ # - Cappuccino
603
+ # - Latte
604
+ # - Mocha
605
+ # EOY
606
+ #
607
+ # parser = Psychgus.parser(stylers: CoffeeStyler.new)
608
+ # parser.parse(coffee)
609
+ # puts parser.handler.root.to_yaml
610
+ #
611
+ # # Output:
612
+ # # Coffee:
613
+ # # Roast: [Light, Medium, Dark]
614
+ # # Style: [Cappuccino, Latte, Mocha]
615
+ #
616
+ # @param stylers [nil,Styler,Array<Styler>] the Styler(s) to use when parsing the YAML
617
+ # @param deref_aliases [true,false] whether to dereference aliases; output the actual value
618
+ # instead of the alias
619
+ #
620
+ # @return [Psych::Parser] the new styled Parser
621
+ #
622
+ # @see StyledTreeBuilder
623
+ # @see Psych.parser
624
+ def self.parser(stylers: nil,deref_aliases: false,**options)
625
+ return Psych::Parser.new(StyledTreeBuilder.new(*stylers,deref_aliases: deref_aliases,**options))
626
+ end
627
+
628
+ ###
629
+ # Unnecessary Methods
630
+ #
631
+ # All of the below methods are not needed, but are defined
632
+ # so that Psychgus can be a drop-in replacement for Psych.
633
+ #
634
+ # Instead, you should probably use Psych.
635
+ # This is also the recommended practice in case your version
636
+ # of Psych defines the method differently.
637
+ #
638
+ # Private methods of Psych are not defined.
639
+ #
640
+ # Because extend is used, do not prefix methods with "self."
641
+ #
642
+ # @author Jonathan Bradley Whited (@esotericpig)
643
+ # @since 1.0.0
644
+ ###
645
+ module PsychDropIn
646
+ # @see Psych.add_builtin_type
647
+ def add_builtin_type(*args,&block)
648
+ Psych.add_builtin_type(*args,&block)
649
+ end
650
+
651
+ # @see Psych.add_domain_type
652
+ def add_domain_type(*args,&block)
653
+ Psych.add_domain_type(*args,&block)
654
+ end
655
+
656
+ # @see Psych.add_tag
657
+ def add_tag(*args)
658
+ Psych.add_tag(*args)
659
+ end
660
+
661
+ # @see Psych.load
662
+ def load(*args,**kargs)
663
+ Psych.load(*args,**kargs)
664
+ end
665
+
666
+ # @see Psych.load_file
667
+ def load_file(*args,**kargs)
668
+ Psych.load_file(*args,**kargs)
669
+ end
670
+
671
+ # @see Psych.load_stream
672
+ def load_stream(*args,**kargs)
673
+ Psych.load_stream(*args,**kargs)
674
+ end
675
+
676
+ # @see Psych.remove_type
677
+ def remove_type(*args)
678
+ Psych.remove_type(*args)
679
+ end
680
+
681
+ # @see Psych.safe_load
682
+ def safe_load(*args,**kargs)
683
+ Psych.safe_load(*args,**kargs)
684
+ end
685
+
686
+ # @see Psych.to_json
687
+ def to_json(*args)
688
+ Psych.to_json(*args)
689
+ end
690
+ end
691
+
692
+ extend PsychDropIn
693
+ end