psychgus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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