psychgus 1.0.1 → 1.2.0

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