psychgus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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