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.
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ #--
5
+ # This file is part of Psychgus.
6
+ # Copyright (c) 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 'psychgus/super_sniffer/parent'
24
+
25
+ module Psychgus
26
+ class SuperSniffer
27
+ ###
28
+ # An empty {SuperSniffer} used for speed when you don't need sniffing in {StyledTreeBuilder}.
29
+ #
30
+ # @author Jonathan Bradley Whited (@esotericpig)
31
+ # @since 1.0.0
32
+ ###
33
+ class Empty < SuperSniffer
34
+ def initialize(*) end
35
+ def add_alias(*) end
36
+ def add_scalar(*) end
37
+ def end_document(*) end
38
+ def end_mapping(*) end
39
+ def end_sequence(*) end
40
+ def end_stream(*) end
41
+ def start_document(*) end
42
+ def start_mapping(*) end
43
+ def start_sequence(*) end
44
+ def start_stream(*) end
45
+ end
46
+ end
47
+
48
+ ###
49
+ # This is used in {StyledTreeBuilder} to "sniff" information about the YAML.
50
+ #
51
+ # Then this information can be used in a {Styler} and/or a {Blueberry}.
52
+ #
53
+ # Most information is straightforward:
54
+ # - {#aliases} # Array of Psych::Nodes::Alias processed so far
55
+ # - {#mappings} # Array of Psych::Nodes::Mapping processed so far
56
+ # - {#nodes} # Array of all Psych::Nodes::Node processed so far
57
+ # - {#scalars} # Array of Psych::Nodes::Scalar processed so far
58
+ # - {#sequences} # Array of Psych::Nodes::Sequence processed so far
59
+ #
60
+ # {#parent} is the current {SuperSniffer::Parent} of the node being processed,
61
+ # which is nil for the first node.
62
+ #
63
+ # {#parents} are all of the (grand){SuperSniffer::Parent}(s) for the current node,
64
+ # which is empty for the first node.
65
+ #
66
+ # A parent is a Mapping or Sequence, or a Key (Scalar) in a Mapping.
67
+ #
68
+ # {#level} and {#position} can be best understood by an example.
69
+ #
70
+ # If you have this YAML:
71
+ # Burgers:
72
+ # Classic:
73
+ # Sauce: [Ketchup,Mustard]
74
+ # Cheese: American
75
+ # Bun: Sesame Seed
76
+ # BBQ:
77
+ # Sauce: Honey BBQ
78
+ # Cheese: Cheddar
79
+ # Bun: Kaiser
80
+ # Fancy:
81
+ # Sauce: Spicy Wasabi
82
+ # Cheese: Smoked Gouda
83
+ # Bun: Hawaiian
84
+ # Toppings:
85
+ # - Mushrooms
86
+ # - [Lettuce, Onions, Pickles, Tomatoes]
87
+ # - [[Ketchup,Mustard], [Salt,Pepper]]
88
+ #
89
+ # Then the levels and positions will be as follows:
90
+ # # (level:position):current_node - <parent:(parent_level:parent_position)>
91
+ #
92
+ # (1:1):Psych::Nodes::Stream - <nil>
93
+ # (1:1):Psych::Nodes::Document - <stream:(1:1)>
94
+ # (1:1):Psych::Nodes::Mapping - <doc:(1:1)>
95
+ # (2:1):Burgers - <map:(1:1)>
96
+ # (3:1):Psych::Nodes::Mapping - <Burgers:(2:1)>
97
+ # (4:1):Classic - <map:(3:1)>
98
+ # (5:1):Psych::Nodes::Mapping - <Classic:(4:1)>
99
+ # (6:1):Sauce - <map:(5:1)>
100
+ # (7:1):Psych::Nodes::Sequence - <Sauce:(6:1)>
101
+ # (8:1):Ketchup - <seq:(7:1)>
102
+ # (8:2):Mustard - <seq:(7:1)>
103
+ # (6:2):Cheese - <map:(5:1)>
104
+ # (7:1):American - <Cheese:(6:2)>
105
+ # (6:3):Bun - <map:(5:1)>
106
+ # (7:1):Sesame Seed - <Bun:(6:3)>
107
+ # (4:2):BBQ - <map:(3:1)>
108
+ # (5:1):Psych::Nodes::Mapping - <BBQ:(4:2)>
109
+ # (6:1):Sauce - <map:(5:1)>
110
+ # (7:1):Honey BBQ - <Sauce:(6:1)>
111
+ # (6:2):Cheese - <map:(5:1)>
112
+ # (7:1):Cheddar - <Cheese:(6:2)>
113
+ # (6:3):Bun - <map:(5:1)>
114
+ # (7:1):Kaiser - <Bun:(6:3)>
115
+ # (4:3):Fancy - <map:(3:1)>
116
+ # (5:1):Psych::Nodes::Mapping - <Fancy:(4:3)>
117
+ # (6:1):Sauce - <map:(5:1)>
118
+ # (7:1):Spicy Wasabi - <Sauce:(6:1)>
119
+ # (6:2):Cheese - <map:(5:1)>
120
+ # (7:1):Smoked Gouda - <Cheese:(6:2)>
121
+ # (6:3):Bun - <map:(5:1)>
122
+ # (7:1):Hawaiian - <Bun:(6:3)>
123
+ # (2:2):Toppings - <map:(1:1)>
124
+ # (3:1):Psych::Nodes::Sequence - <Toppings:(2:2)>
125
+ # (4:1):Mushrooms - <seq:(3:1)>
126
+ # (4:2):Psych::Nodes::Sequence - <seq:(3:1)>
127
+ # (5:1):Lettuce - <seq:(4:2)>
128
+ # (5:2):Onions - <seq:(4:2)>
129
+ # (5:3):Pickles - <seq:(4:2)>
130
+ # (5:4):Tomatoes - <seq:(4:2)>
131
+ # (4:3):Psych::Nodes::Sequence - <seq:(3:1)>
132
+ # (5:1):Psych::Nodes::Sequence - <seq:(4:3)>
133
+ # (6:1):Ketchup - <seq:(5:1)>
134
+ # (6:2):Mustard - <seq:(5:1)>
135
+ # (5:2):Psych::Nodes::Sequence - <seq:(4:3)>
136
+ # (6:1):Salt - <seq:(5:2)>
137
+ # (6:2):Pepper - <seq:(5:2)>
138
+ #
139
+ # The "Super Sniffer" is the nickname for Gus's nose from the TV show Psych
140
+ # because he has a very refined sense of smell.
141
+ #
142
+ # @note You should never call the methods that are not readers, like {#add_alias}, {#start_mapping}, etc.
143
+ # unless you are extending this class (creating a subclass).
144
+ #
145
+ # @author Jonathan Bradley Whited (@esotericpig)
146
+ # @since 1.0.0
147
+ #
148
+ # @see StyledTreeBuilder
149
+ # @see Styler
150
+ # @see Blueberry#psychgus_stylers
151
+ ###
152
+ class SuperSniffer
153
+ EMPTY = Empty.new().freeze()
154
+
155
+ attr_reader :aliases
156
+ attr_reader :documents
157
+ attr_reader :level
158
+ attr_reader :mappings
159
+ attr_reader :nodes
160
+ attr_reader :parent
161
+ attr_reader :parents
162
+ attr_reader :position
163
+ attr_reader :scalars
164
+ attr_reader :sequences
165
+ attr_reader :streams
166
+
167
+ # Initialize this class for sniffing.
168
+ def initialize()
169
+ @aliases = []
170
+ @documents = []
171
+ @level = 1
172
+ @mappings = []
173
+ @nodes = []
174
+ @parent = nil
175
+ @parents = []
176
+ @position = 1
177
+ @scalars = []
178
+ @sequences = []
179
+ @streams = []
180
+ end
181
+
182
+ # Add a Psych::Nodes::Alias to this class only (not to the YAML).
183
+ #
184
+ # A {Styler} should probably never call this.
185
+ #
186
+ # @param node [Psych::Nodes::Alias] the alias to add
187
+ #
188
+ # @see add_child
189
+ def add_alias(node)
190
+ add_child(node)
191
+ @aliases.push(node)
192
+ end
193
+
194
+ # Add a Psych::Nodes::Scalar to this class only (not to the YAML).
195
+ #
196
+ # A {Styler} should probably never call this.
197
+ #
198
+ # @param node [Psych::Nodes::Scalar] the scalar to add
199
+ #
200
+ # @see add_child
201
+ def add_scalar(node)
202
+ add_child(node)
203
+ @scalars.push(node)
204
+ end
205
+
206
+ # End a Psych::Nodes::Document started with {#start_document}.
207
+ #
208
+ # Pops off a parent from {#parents} and sets {#parent} to the last one.
209
+ # {#level} and {#position} are reset according to the last parent.
210
+ #
211
+ # A {Styler} should probably never call this.
212
+ def end_document()
213
+ end_parent(top_level: true)
214
+ end
215
+
216
+ # End a Psych::Nodes::Mapping started with {#start_mapping}.
217
+ #
218
+ # Pops off a parent from {#parents} and sets {#parent} to the last one.
219
+ # {#level} and {#position} are reset according to the last parent.
220
+ #
221
+ # A {Styler} should probably never call this.
222
+ #
223
+ # @see end_parent
224
+ def end_mapping()
225
+ end_parent(mapping_value: true)
226
+ end
227
+
228
+ # End a Psych::Nodes::Sequence started with {#start_sequence}.
229
+ #
230
+ # Pops off a parent from {#parents} and sets {#parent} to the last one.
231
+ # {#level} and {#position} are reset according to the last parent.
232
+ #
233
+ # A {Styler} should probably never call this.
234
+ #
235
+ # @see end_parent
236
+ def end_sequence()
237
+ end_parent(mapping_value: true)
238
+ end
239
+
240
+ # End a Psych::Nodes::Stream started with {#start_stream}.
241
+ #
242
+ # Pops off a parent from {#parents} and sets {#parent} to the last one.
243
+ # {#level} and {#position} are reset according to the last parent.
244
+ #
245
+ # A {Styler} should probably never call this.
246
+ def end_stream()
247
+ end_parent(top_level: true)
248
+ end
249
+
250
+ # Start a Psych::Nodes::Document.
251
+ #
252
+ # Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}.
253
+ # {#level} and {#position} are incremented/set accordingly.
254
+ #
255
+ # A {Styler} should probably never call this.
256
+ #
257
+ # @param node [Psych::Nodes::Document] the Document to start
258
+ #
259
+ # @see start_parent
260
+ def start_document(node)
261
+ start_parent(node,debug_tag: :doc,top_level: true)
262
+ @documents.push(node)
263
+ end
264
+
265
+ # Start a Psych::Nodes::Mapping.
266
+ #
267
+ # Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}.
268
+ # {#level} and {#position} are incremented/set accordingly.
269
+ #
270
+ # A {Styler} should probably never call this.
271
+ #
272
+ # @param node [Psych::Nodes::Mapping] the Mapping to start
273
+ #
274
+ # @see start_parent
275
+ def start_mapping(node)
276
+ start_parent(node,debug_tag: :map,child_type: :key)
277
+ @mappings.push(node)
278
+ end
279
+
280
+ # Start a Psych::Nodes::Sequence.
281
+ #
282
+ # Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}.
283
+ # {#level} and {#position} are incremented/set accordingly.
284
+ #
285
+ # A {Styler} should probably never call this.
286
+ #
287
+ # @param node [Psych::Nodes::Sequence] the Sequence to start
288
+ #
289
+ # @see start_parent
290
+ def start_sequence(node)
291
+ start_parent(node,debug_tag: :seq)
292
+ @sequences.push(node)
293
+ end
294
+
295
+ # Start a Psych::Nodes::Stream.
296
+ #
297
+ # Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}.
298
+ # {#level} and {#position} are incremented/set accordingly.
299
+ #
300
+ # A {Styler} should probably never call this.
301
+ #
302
+ # @param node [Psych::Nodes::Stream] the Stream to start
303
+ #
304
+ # @see start_parent
305
+ def start_stream(node)
306
+ start_parent(node,debug_tag: :stream,top_level: true)
307
+ @streams.push(node)
308
+ end
309
+
310
+ protected
311
+
312
+ # Add a non-parent node.
313
+ #
314
+ # This will increment {#position} accordingly, and if the child is a Key to a Mapping,
315
+ # create a fake "{SuperSniffer::Parent}".
316
+ #
317
+ # @param node [Psych::Nodes::Node] the non-parent Node to add
318
+ #
319
+ # @see end_mapping_value
320
+ def add_child(node)
321
+ if !@parent.nil?()
322
+ # Fake a "parent" if necessary
323
+ case @parent.child_type
324
+ when :key
325
+ start_mapping_key(node)
326
+ return
327
+ when :value
328
+ end_mapping_value()
329
+ return
330
+ else
331
+ @parent.child_position += 1
332
+ end
333
+ end
334
+
335
+ @position += 1
336
+
337
+ @nodes.push(node)
338
+ end
339
+
340
+ # End a fake "{SuperSniffer::Parent}" that is a Key/Value to a Mapping.
341
+ #
342
+ # @see add_child
343
+ def end_mapping_value()
344
+ end_parent() # Do not pass in "mapping_value: true" and/or "top_level: true"
345
+
346
+ @parent.child_type = :key unless @parent.nil?()
347
+ end
348
+
349
+ # End a {SuperSniffer::Parent}.
350
+ #
351
+ # Pops off a parent from {#parents} and sets {#parent} to the last one.
352
+ # {#level} and {#position} are reset according to the last parent.
353
+ #
354
+ # @param mapping_value [true,false] true if parent can be the value of a Mapping's key
355
+ # @param top_level [true,false] true if a top-level parent (i.e., encapsulating the main data)
356
+ def end_parent(mapping_value: false,top_level: false)
357
+ @parents.pop()
358
+ @parent = @parents.last
359
+
360
+ @level = top_level ? 1 : (@level - 1)
361
+
362
+ if !@parent.nil?()
363
+ @parent.child_position += 1
364
+ @position = @parent.child_position
365
+
366
+ # add_child() will not be called again, so end a fake "parent" manually with a fake "value"
367
+ # - This is necessary for any parents that can be the value of a map's key (e.g., Sequence)
368
+ end_mapping_value() if mapping_value && !@parent.child_type.nil?()
369
+ end
370
+ end
371
+
372
+ # Start a fake "{SuperSniffer::Parent}" that is a Key/Value to a Mapping.
373
+ #
374
+ # Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}.
375
+ # {#level} and {#position} are incremented/set accordingly.
376
+ #
377
+ # @param node [Psych::Nodes::Node] the Node to start
378
+ #
379
+ # @see start_parent
380
+ def start_mapping_key(node)
381
+ debug_tag = nil
382
+
383
+ # Value must be first because Scalar also has an anchor
384
+ if node.respond_to?(:value)
385
+ debug_tag = node.value
386
+ elsif node.respond_to?(:anchor)
387
+ debug_tag = node.anchor
388
+ end
389
+
390
+ debug_tag = :noface if debug_tag.nil?()
391
+
392
+ start_parent(node,debug_tag: debug_tag,child_type: :value)
393
+ end
394
+
395
+ # Start a {SuperSniffer::Parent}.
396
+ #
397
+ # Creates a {SuperSniffer::Parent}, sets {#parent} to it, and adds it to {#parents}.
398
+ # {#level} and {#position} are incremented/set accordingly.
399
+ #
400
+ # @param node [Psych::Nodes::Node] the parent Node to start
401
+ # @param top_level [true,false] true if a top-level parent (i.e., encapsulating the main data)
402
+ # @param extra [Hash] the extra keyword args to pass to {SuperSniffer::Parent#initialize}
403
+ #
404
+ # @see SuperSniffer::Parent#initialize
405
+ def start_parent(node,top_level: false,**extra)
406
+ @parent = Parent.new(self,node,**extra)
407
+
408
+ @parents.push(@parent)
409
+ @nodes.push(node)
410
+
411
+ if top_level
412
+ @level = 1
413
+ @position = @parent.position
414
+ else
415
+ @level += 1
416
+ @position = 1
417
+ end
418
+ end
419
+ end
420
+ end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # This file is part of Psychgus.
7
+ # Copyright (c) 2017-2019 Jonathan Bradley Whited (@esotericpig)
8
+ #
9
+ # Psychgus is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU Lesser General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # Psychgus is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU Lesser General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU Lesser General Public License
20
+ # along with Psychgus. If not, see <http://www.gnu.org/licenses/>.
21
+ #++
22
+
23
+
24
+ module Psychgus
25
+ # Version of this gem in "#.#.#" format
26
+ VERSION = '1.0.0'
27
+ end