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.
- checksums.yaml +7 -0
- data/Gemfile +25 -0
- data/LICENSE.txt +165 -0
- data/README.md +507 -0
- data/Rakefile +158 -0
- data/lib/psychgus/blueberry.rb +110 -0
- data/lib/psychgus/ext/core_ext.rb +77 -0
- data/lib/psychgus/ext/node_ext.rb +68 -0
- data/lib/psychgus/ext/yaml_tree_ext.rb +101 -0
- data/lib/psychgus/ext.rb +32 -0
- data/lib/psychgus/styled_document_stream.rb +66 -0
- data/lib/psychgus/styled_tree_builder.rb +294 -0
- data/lib/psychgus/styler.rb +127 -0
- data/lib/psychgus/super_sniffer/parent.rb +218 -0
- data/lib/psychgus/super_sniffer.rb +420 -0
- data/lib/psychgus/version.rb +27 -0
- data/lib/psychgus.rb +693 -0
- data/psychgus.gemspec +121 -0
- data/test/blueberry_test.rb +141 -0
- data/test/psychgus_test.rb +142 -0
- data/test/psychgus_tester.rb +90 -0
- data/test/sniffer_test.rb +287 -0
- data/test/styler_test.rb +85 -0
- data/yard/templates/default/layout/html/footer.erb +5 -0
- metadata +182 -0
@@ -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
|