psychgus 1.0.1 → 1.2.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.
@@ -50,13 +50,14 @@ module Psychgus
50
50
  module YAMLTreeExt
51
51
  # Accepts a new Object to convert to YAML.
52
52
  #
53
- # This is roughly the same place where Psych checks if +target+ responds to :encode_with.
53
+ # This is roughly the same place where Psych checks if +target+ responds to +:encode_with+.
54
54
  #
55
- # This will check if @emitter is a {StyledTreeBuilder} and if +target+ is a {Blueberry}.
56
- # 1. If the above is true, get the {Styler}(s) and add them to @emitter.
57
- # 2. Call super.
58
- # 3. If the above is true, remove the {Styler}(s) from @emitter.
59
- # 4. Return the result of super.
55
+ # 1. Check if +@emitter+ is a {StyledTreeBuilder}.
56
+ # 2. If #1 and +target+ is a {Blueberry}, get the {Styler}(s) from +target+ and add them to +@emitter+.
57
+ # 3. If #1 and +@emitter.deref_aliases?+, prevent +target+ from becoming an alias.
58
+ # 4. Call +super+ and store the result.
59
+ # 5. If #2, remove the {Styler}(s) from +@emitter+.
60
+ # 6. Return the result of +super+.
60
61
  #
61
62
  # @param target [Object] the Object to pass to super
62
63
  #
@@ -64,6 +65,7 @@ module Psychgus
64
65
  #
65
66
  # @see Psych::Visitors::YAMLTree
66
67
  # @see Blueberry
68
+ # @see Blueberry#psychgus_stylers
67
69
  # @see Styler
68
70
  # @see StyledTreeBuilder
69
71
  def accept(target)
@@ -0,0 +1,273 @@
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/styler'
24
+ require 'psychgus/super_sniffer'
25
+
26
+ require 'stringio'
27
+
28
+ module Psychgus
29
+ ###
30
+ # A collection of commonly-used {Styler} mixins
31
+ # that can be included in a class instead of {Styler}.
32
+ #
33
+ # @author Jonathan Bradley Whited (@esotericpig)
34
+ # @since 1.2.0
35
+ #
36
+ # @see Stylers
37
+ # @see Styler
38
+ ###
39
+ module Stylables
40
+ ###
41
+ # A helper mixin for Stylables that change a node's style.
42
+ #
43
+ # There is no max level, because a parent's style will override all of its children.
44
+ ###
45
+ module StyleStylable
46
+ include Styler
47
+
48
+ attr_accessor :min_level # @return [Integer] the minimum level (inclusive) to style
49
+ attr_accessor :new_style # @return [Integer] the new style to set the nodes to
50
+
51
+ # +max_level+ is not defined because a parent's style will override all of its children.
52
+ #
53
+ # @param min_level [Integer] the minimum level (inclusive) to style
54
+ # @param new_style [Integer] the new style to set the nodes to
55
+ # @param kargs [Hash] capture extra keyword args, so no error for undefined args
56
+ def initialize(min_level=0,new_style: nil,**kargs)
57
+ @min_level = min_level
58
+ @new_style = new_style
59
+ end
60
+
61
+ # Change the style of +node+ to {new_style} if it is >= {min_level}.
62
+ def change_style(sniffer,node)
63
+ return unless node.respond_to?(:style=)
64
+
65
+ node.style = @new_style if sniffer.level >= @min_level
66
+ end
67
+ end
68
+ end
69
+
70
+ module Stylables
71
+ ###
72
+ # (see Stylers::CapStyler)
73
+ ###
74
+ module CapStylable
75
+ include Styler
76
+
77
+ attr_reader :delim # @return [String,Regexp] the delimiter to split on
78
+ attr_accessor :each_word # @return [true,false] whether to capitalize each word separated by {delim}
79
+ attr_accessor :new_delim # @return [nil,String] the replacement for each {delim} if not nil
80
+
81
+ # @param each_word [true,false] whether to capitalize each word separated by +delim+
82
+ # @param new_delim [nil,String] the replacement for each +delim+ if not nil
83
+ # @param delim [String,Regexp] the delimiter to split on
84
+ # @param kargs [Hash] capture extra keyword args, so no error for undefined args
85
+ def initialize(each_word: true,new_delim: nil,delim: /[\s_\-]/,**kargs)
86
+ delim = Regexp.quote(delim.to_s()) unless delim.is_a?(Regexp)
87
+
88
+ @delim = Regexp.new("(#{delim.to_s()})")
89
+ @each_word = each_word
90
+ @new_delim = new_delim
91
+ end
92
+
93
+ # Capitalize an individual word (not words).
94
+ #
95
+ # This method can safely be overridden with a new implementation.
96
+ #
97
+ # @param word [nil,String] the word to capitalize
98
+ #
99
+ # @return [String] the capitalized word
100
+ def cap_word(word)
101
+ return word if word.nil?() || word.empty?()
102
+
103
+ # Already capitalized, good for all-capitalized words, like 'BBQ'
104
+ return word if word[0] == word[0].upcase()
105
+
106
+ return word.capitalize()
107
+ end
108
+
109
+ # Capitalize +node.value+.
110
+ #
111
+ # @see cap_word
112
+ # @see Styler#style_scalar
113
+ def style_scalar(sniffer,node)
114
+ if !@each_word || node.value.nil?() || node.value.empty?()
115
+ node.value = cap_word(node.value)
116
+ return
117
+ end
118
+
119
+ is_delim = false
120
+
121
+ node.value = node.value.split(@delim).map() do |v|
122
+ if is_delim
123
+ v = @new_delim unless @new_delim.nil?()
124
+ else
125
+ v = cap_word(v)
126
+ end
127
+
128
+ is_delim = !is_delim
129
+ v
130
+ end.join()
131
+ end
132
+ end
133
+
134
+ ###
135
+ # (see Stylers::HierarchyStyler)
136
+ ###
137
+ module HierarchyStylable
138
+ include Styler
139
+
140
+ attr_accessor :io # @return [IO] the IO to write to; defaults to StringIO
141
+ attr_accessor :verbose # @return [true,false] whether to be more verbose (e.g., write child info)
142
+
143
+ # @param io [IO] the IO to write to
144
+ # @param verbose [true,false] whether to be more verbose (e.g., write child info)
145
+ # @param kargs [Hash] capture extra keyword args, so no error for undefined args
146
+ def initialize(io: StringIO.new(),verbose: false,**kargs)
147
+ @io = io
148
+ @verbose = verbose
149
+ end
150
+
151
+ # Write the hierarchy of +node+ to {io}.
152
+ #
153
+ # @see Styler#style
154
+ def style(sniffer,node)
155
+ @io.print (' ' * (sniffer.level - 1))
156
+
157
+ name = node.respond_to?(:value) ? node.value : node.class.name
158
+ parent = sniffer.parent
159
+
160
+ @io.print "(#{sniffer.level}:#{sniffer.position}):#{name} - "
161
+
162
+ if @verbose
163
+ @io.print parent
164
+ else
165
+ @io.print "<#{parent.debug_tag}:(#{parent.level}:#{parent.position})>"
166
+ end
167
+
168
+ @io.puts
169
+ end
170
+
171
+ # Convert {io} to a String if possible (e.g., StringIO).
172
+ #
173
+ # @return [String] the IO String result or just {io} as a String
174
+ def to_s()
175
+ return @io.respond_to?(:string) ? @io.string : @io.to_s()
176
+ end
177
+ end
178
+
179
+ ###
180
+ # (see Stylers::MapFlowStyler)
181
+ ###
182
+ module MapFlowStylable
183
+ include StyleStylable
184
+
185
+ # (see StyleStylable#initialize)
186
+ # @!method initialize(min_level=0,new_style: nil,**kargs)
187
+ #
188
+ # If +new_style+ is nil (the default), then {MAPPING_FLOW} will be used.
189
+ def initialize(*)
190
+ super
191
+
192
+ @new_style = MAPPING_FLOW if @new_style.nil?()
193
+ end
194
+
195
+ # Change the style of a Mapping to FLOW (or to the value of {new_style})
196
+ # if it is >= {min_level}.
197
+ #
198
+ # @see change_style
199
+ # @see Styler#style_mapping
200
+ def style_mapping(sniffer,node)
201
+ change_style(sniffer,node)
202
+ end
203
+ end
204
+
205
+ ###
206
+ # (see Stylers::NoSymStyler)
207
+ ###
208
+ module NoSymStylable
209
+ include Styler
210
+
211
+ attr_accessor :cap # @return [true,false] whether to capitalize the symbol
212
+
213
+ alias_method :cap?,:cap
214
+
215
+ # @param cap [true,false] whether to capitalize the symbol
216
+ # @param kargs [Hash] capture extra keyword args, so no error for undefined args
217
+ def initialize(cap: true,**kargs)
218
+ @cap = cap
219
+ end
220
+
221
+ # If +node.value+ is a symbol, change it into a string and capitalize it.
222
+ #
223
+ # @see Styler#style_scalar
224
+ def style_scalar(sniffer,node)
225
+ return if node.value.nil?() || node.value.empty?()
226
+ return if node.value[0] != ':'
227
+
228
+ node.value = node.value[1..-1]
229
+ node.value = node.value.capitalize() if @cap
230
+ end
231
+ end
232
+
233
+ ###
234
+ # (see Stylers::NoTagStyler)
235
+ ###
236
+ module NoTagStylable
237
+ include Styler
238
+
239
+ # If +node.tag+ is settable, set it to nil.
240
+ #
241
+ # @see Styler#style
242
+ def style(sniffer,node)
243
+ node.tag = nil if node.respond_to?(:tag=)
244
+ end
245
+ end
246
+
247
+ ###
248
+ # (see Stylers::SeqFlowStyler)
249
+ ###
250
+ module SeqFlowStylable
251
+ include StyleStylable
252
+
253
+ # (see StyleStylable#initialize)
254
+ # @!method initialize(min_level=0,new_style: nil,**kargs)
255
+ #
256
+ # If +new_style+ is nil (the default), then {SEQUENCE_FLOW} will be used.
257
+ def initialize(*)
258
+ super
259
+
260
+ @new_style = SEQUENCE_FLOW if @new_style.nil?()
261
+ end
262
+
263
+ # Change the style of a Sequence to FLOW (or to the value of {new_style})
264
+ # if it is >= {min_level}.
265
+ #
266
+ # @see change_style
267
+ # @see Styler#style_sequence
268
+ def style_sequence(sniffer,node)
269
+ change_style(sniffer,node)
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,308 @@
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/stylables'
24
+
25
+ module Psychgus
26
+ ###
27
+ # A collection of commonly-used {Styler} classes.
28
+ #
29
+ # @example
30
+ # require 'psychgus'
31
+ #
32
+ # class EggCarton
33
+ # def initialize
34
+ # @eggs = {
35
+ # :styles => ['fried', 'scrambled', ['BBQ', 'ketchup & mustard']],
36
+ # :colors => ['brown', 'white', ['blue', 'green']]
37
+ # }
38
+ # end
39
+ # end
40
+ #
41
+ # hierarchy = Psychgus::HierarchyStyler.new(io: $stdout)
42
+ #
43
+ # puts EggCarton.new.to_yaml(stylers: [
44
+ # Psychgus::NoSymStyler.new,
45
+ # Psychgus::NoTagStyler.new,
46
+ # Psychgus::CapStyler.new,
47
+ # Psychgus::FlowStyler.new(4),
48
+ # hierarchy
49
+ # ])
50
+ #
51
+ # # Output:
52
+ # # ---
53
+ # # Eggs:
54
+ # # Styles: [Fried, Scrambled, [BBQ, Ketchup & Mustard]]
55
+ # # Colors: [Brown, White, [Blue, Green]]
56
+ #
57
+ # # (1:1):Psych::Nodes::Stream - <root:(0:0)>
58
+ # # (1:1):Psych::Nodes::Document - <stream:(1:1)>
59
+ # # (1:1):Psych::Nodes::Mapping - <doc:(1:1)>
60
+ # # (2:1):Eggs - <map:(1:1)>
61
+ # # (3:1):Psych::Nodes::Mapping - <Eggs:(2:1)>
62
+ # # (4:1):Styles - <map:(3:1)>
63
+ # # (5:1):Psych::Nodes::Sequence - <Styles:(4:1)>
64
+ # # (6:1):Fried - <seq:(5:1)>
65
+ # # (6:2):Scrambled - <seq:(5:1)>
66
+ # # (6:3):Psych::Nodes::Sequence - <seq:(5:1)>
67
+ # # (7:1):BBQ - <seq:(6:3)>
68
+ # # (7:2):Ketchup & Mustard - <seq:(6:3)>
69
+ # # (4:2):Colors - <map:(3:1)>
70
+ # # (5:1):Psych::Nodes::Sequence - <Colors:(4:2)>
71
+ # # (6:1):Brown - <seq:(5:1)>
72
+ # # (6:2):White - <seq:(5:1)>
73
+ # # (6:3):Psych::Nodes::Sequence - <seq:(5:1)>
74
+ # # (7:1):Blue - <seq:(6:3)>
75
+ # # (7:2):Green - <seq:(6:3)>
76
+ #
77
+ # @author Jonathan Bradley Whited (@esotericpig)
78
+ # @since 1.2.0
79
+ #
80
+ # @see Stylables
81
+ # @see Styler
82
+ ###
83
+ module Stylers
84
+ ###
85
+ # A Capitalizer for Scalars.
86
+ #
87
+ # @example
88
+ # require 'psychgus'
89
+ #
90
+ # data = {
91
+ # 'eggs' => [
92
+ # 'omelette',
93
+ # 'BBQ eggs',
94
+ # 'hard-boiled eggs',
95
+ # 'soft_boiled eggs',
96
+ # 'fried@eggs'
97
+ # ]}
98
+ #
99
+ # seq_flow = Psychgus::SeqFlowStyler.new
100
+ #
101
+ # puts data.to_yaml(stylers: [Psychgus::CapStyler.new,seq_flow])
102
+ #
103
+ # # Output:
104
+ # # ---
105
+ # # Eggs: [Omelette, BBQ Eggs, Hard-Boiled Eggs, Soft_Boiled Eggs, Fried@eggs]
106
+ #
107
+ # puts data.to_yaml(stylers: [Psychgus::CapStyler.new(each_word: false),seq_flow])
108
+ #
109
+ # # Output:
110
+ # # ---
111
+ # # Eggs: [Omelette, BBQ eggs, Hard-boiled eggs, Soft_boiled eggs, Fried@eggs]
112
+ #
113
+ # puts data.to_yaml(stylers: [Psychgus::CapStyler.new(new_delim: '(o)'),seq_flow])
114
+ #
115
+ # # Output:
116
+ # # ---
117
+ # # Eggs: [Omelette, BBQ(o)Eggs, Hard(o)Boiled(o)Eggs, Soft(o)Boiled(o)Eggs, Fried@eggs]
118
+ #
119
+ # class Cappie
120
+ # include Psychgus::CapStylable
121
+ #
122
+ # def cap_word(word)
123
+ # return 'bbq' if word.casecmp('BBQ') == 0
124
+ #
125
+ # super(word)
126
+ # end
127
+ # end
128
+ #
129
+ # puts data.to_yaml(stylers: [Cappie.new(new_delim: '*',delim: /[\s@]/),seq_flow])
130
+ #
131
+ # # Output:
132
+ # # ---
133
+ # # Eggs: [Omelette, bbq*Eggs, Hard-boiled*Eggs, Soft_boiled*Eggs, Fried*Eggs]
134
+ #
135
+ # @see Stylables::CapStylable
136
+ ###
137
+ class CapStyler
138
+ include Stylables::CapStylable
139
+ end
140
+
141
+ ###
142
+ # A FLOW style changer for Mappings & Sequences.
143
+ #
144
+ # @example
145
+ # require 'psychgus'
146
+ #
147
+ # data = {
148
+ # 'Eggs' => {
149
+ # 'Styles' => ['Fried', 'Scrambled', ['BBQ', 'Ketchup']],
150
+ # 'Colors' => ['Brown', 'White', ['Blue', 'Green']]
151
+ # }}
152
+ #
153
+ # puts data.to_yaml(stylers: Psychgus::FlowStyler.new)
154
+ #
155
+ # # Output:
156
+ # # --- {Eggs: {Styles: [Fried, Scrambled, [BBQ, Ketchup]], Colors: [Brown, White, [Blue, Green]]}}
157
+ #
158
+ # # >= level 4 (see Psychgus.hierarchy)
159
+ # puts data.to_yaml(stylers: Psychgus::FlowStyler.new(4))
160
+ #
161
+ # # Output:
162
+ # # ---
163
+ # # Eggs:
164
+ # # Styles: [Fried, Scrambled, [BBQ, Ketchup]]
165
+ # # Colors: [Brown, White, [Blue, Green]]
166
+ #
167
+ # # >= level 6 (see Psychgus.hierarchy)
168
+ # puts data.to_yaml(stylers: Psychgus::FlowStyler.new(6))
169
+ #
170
+ # # Output:
171
+ # # ---
172
+ # # Eggs:
173
+ # # Styles:
174
+ # # - Fried
175
+ # # - Scrambled
176
+ # # - [BBQ, Ketchup]
177
+ # # Colors:
178
+ # # - Brown
179
+ # # - White
180
+ # # - [Blue, Green]
181
+ #
182
+ # @see Stylables::MapFlowStylable
183
+ # @see Stylables::SeqFlowStylable
184
+ ###
185
+ class FlowStyler
186
+ include Stylables::MapFlowStylable
187
+ include Stylables::SeqFlowStylable
188
+ end
189
+
190
+ ###
191
+ # A visual hierarchy writer of the levels.
192
+ #
193
+ # This is useful for determining the correct level/position when writing a {Styler}.
194
+ #
195
+ # The default IO is StringIO, but can specify a different one.
196
+ #
197
+ # See {Psychgus.hierarchy} for more details.
198
+ #
199
+ # @see Psychgus.hierarchy
200
+ # @see Stylables::HierarchyStylable
201
+ ###
202
+ class HierarchyStyler
203
+ include Stylables::HierarchyStylable
204
+ end
205
+
206
+ ###
207
+ # A FLOW style changer for Mappings only.
208
+ #
209
+ # @see FlowStyler
210
+ # @see Stylables::MapFlowStylable
211
+ ###
212
+ class MapFlowStyler
213
+ include Stylables::MapFlowStylable
214
+ end
215
+
216
+ ###
217
+ # A Symbol remover for Scalars.
218
+ #
219
+ # @example
220
+ # require 'psychgus'
221
+ #
222
+ # data = {
223
+ # :eggs => {
224
+ # :styles => ['Fried', 'Scrambled', ['BBQ', 'Ketchup']],
225
+ # :colors => ['Brown', 'White', ['Blue', 'Green']]
226
+ # }}
227
+ #
228
+ # flow = Psychgus::FlowStyler.new(4)
229
+ #
230
+ # puts data.to_yaml(stylers: [Psychgus::NoSymStyler.new,flow])
231
+ #
232
+ # # Output:
233
+ # # ---
234
+ # # Eggs:
235
+ # # Styles: [Fried, Scrambled, [BBQ, Ketchup]]
236
+ # # Colors: [Brown, White, [Blue, Green]]
237
+ #
238
+ # puts data.to_yaml(stylers: [Psychgus::NoSymStyler.new(cap: false),flow])
239
+ #
240
+ # # ---
241
+ # # eggs:
242
+ # # styles: [Fried, Scrambled, [BBQ, Ketchup]]
243
+ # # colors: [Brown, White, [Blue, Green]]
244
+ #
245
+ # @see Stylables::NoSymStylable
246
+ ###
247
+ class NoSymStyler
248
+ include Stylables::NoSymStylable
249
+ end
250
+
251
+ ###
252
+ # A Tag remover for classes.
253
+ #
254
+ # @example
255
+ # require 'psychgus'
256
+ #
257
+ # class Eggs
258
+ # def initialize
259
+ # @styles = ['Fried', 'Scrambled', ['BBQ', 'Ketchup']]
260
+ # @colors = ['Brown', 'White', ['Blue', 'Green']]
261
+ # end
262
+ # end
263
+ #
264
+ # class EggCarton
265
+ # include Psychgus::Blueberry
266
+ #
267
+ # def initialize
268
+ # @eggs = Eggs.new
269
+ # end
270
+ #
271
+ # def psychgus_stylers(sniffer)
272
+ # Psychgus::FlowStyler.new(4)
273
+ # end
274
+ # end
275
+ #
276
+ # puts EggCarton.new.to_yaml
277
+ #
278
+ # # Output:
279
+ # # --- !ruby/object:EggCarton
280
+ # # eggs: !ruby/object:Eggs
281
+ # # styles: [Fried, Scrambled, [BBQ, Ketchup]]
282
+ # # colors: [Brown, White, [Blue, Green]]
283
+ #
284
+ # puts EggCarton.new.to_yaml(stylers: Psychgus::NoTagStyler.new)
285
+ #
286
+ # # Output:
287
+ # # ---
288
+ # # eggs:
289
+ # # styles: [Fried, Scrambled, [BBQ, Ketchup]]
290
+ # # colors: [Brown, White, [Blue, Green]]
291
+ #
292
+ # @see Stylables::NoTagStylable
293
+ ###
294
+ class NoTagStyler
295
+ include Stylables::NoTagStylable
296
+ end
297
+
298
+ ###
299
+ # A FLOW style changer for Sequences only.
300
+ #
301
+ # @see FlowStyler
302
+ # @see Stylables::SeqFlowStylable
303
+ ###
304
+ class SeqFlowStyler
305
+ include Stylables::SeqFlowStylable
306
+ end
307
+ end
308
+ end