perception 0.1.5

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,231 @@
1
+
2
+ require File.join(File.dirname(__FILE__), '..', 'perception' ) if $0 == __FILE__
3
+
4
+ module Perception #:nodoc
5
+
6
+
7
+ # Eine SeeSession-Instanz entspricht einer Session mit der Konsole.
8
+ class SeeSession
9
+ @@instance = nil
10
+ attr_accessor :time_last, :delayed_newlines, :level, :indent, :out
11
+
12
+ # ------------------------------------------------------------------------------
13
+ # Verwalten
14
+ #
15
+
16
+ # Liefert die eine notwendige Instanz
17
+ def self.instance
18
+ return @@instance if @@instance
19
+ @@instance = SeeSession.new
20
+ @@instance
21
+ end
22
+
23
+
24
+ def initialize
25
+ init
26
+ end
27
+
28
+
29
+ def init
30
+ # früher
31
+ @string_last = '' # enthält den letzten ausgegebenen String (damit man ihn löschen und wiederherstellen kann)
32
+ @time_last = nil # enthält den Zeitpunkt des letzten Prints
33
+ @method_last = nil # enthält die Methode, mit der der letzte String geprinted wurde
34
+ @call_stack_last = 0 # Wie lang war der Stack beim letzten #see?
35
+
36
+ # jetzt
37
+ @speed = nil # nil = unverzögert, 1= lesbar, 2 = lesbar / doppelt so schnell, 0.5 = lesbar / halb so schnell
38
+ @cursor_now = 0 # enthält die aktuelle Position des Cursors
39
+ @method_now = nil # mit welcher Methode wird der aktuelle Print ausgeführt?
40
+ @call_stack_now = 0 # Wie lang ist der Stack beim aktuellen #see?
41
+ @indent = SEE_INDENT_START # sollen die Prints automatisch eingerückt werden?
42
+ @level = SEE_LEVEL_START # aktueller Einrückungslevel
43
+
44
+ # nächstes
45
+ @delayed_newlines = 1 # Mit wie vielen Newline-Zeichen soll der nächste Print beginnen? (Newlines werden verzögert, damit man noch löschen kann)
46
+ @delayed_clear = false # Soll der nächste Print den vorhergehenden überschreiben?
47
+
48
+ @logger = nil # wird erst bei Bedarf erzeugt
49
+ @out = Set.new # Wohin geht der Output? , :log
50
+ @out << :console
51
+ end
52
+
53
+
54
+
55
+
56
+
57
+
58
+ # ------------------------------------------------------------------------------
59
+ # Ausgeben
60
+ #
61
+
62
+ def wait!(string_akt)
63
+ return unless @time_last
64
+ verstrichene_zeit = Time.now - @time_last
65
+ alter_string = @string_last.strip.chomp.scan(/\w+/)
66
+ neuer_string = string_akt.strip.chomp.scan(/\w+/)
67
+ differenz_information = Math.sqrt((alter_string - neuer_string).size + 0.3 )
68
+ time_for_perception = (differenz_information * SEE_PERCEPTION_TIME_PER_WORD * @speed ) / 1000
69
+ if verstrichene_zeit < time_for_perception
70
+ sleep (time_for_perception - verstrichene_zeit)
71
+ end
72
+ nil
73
+ end # wait
74
+
75
+
76
+ def adjust_level
77
+ @call_stack_now = caller.size
78
+ call_stack_diff = @call_stack_now - @call_stack_last
79
+ # puts "call_stack_diff=#{ call_stack_diff }"
80
+ # puts
81
+ if @indent
82
+ if call_stack_diff > 0 && @call_stack_last != 0
83
+ @level += 1
84
+ elsif call_stack_diff < 0 && @level > 1
85
+ @level -= 1
86
+ end
87
+ end
88
+ nil
89
+ end
90
+
91
+
92
+ def clear_indent!
93
+ @call_stack_last = 0 # wie direkt nach der Initialisierung
94
+ @call_stack_now = 0
95
+ @level = SEE_LEVEL_START
96
+ nil
97
+ end
98
+
99
+
100
+
101
+
102
+
103
+ # Gibt das see aus
104
+ def process_print( input, options={} )
105
+
106
+ # vorbereiten
107
+ @method_now = options[:method] || :puts
108
+ self.clear! if @delayed_clear
109
+ self.adjust_level
110
+ newlines = self.process_newline if @delayed_newlines > 0
111
+
112
+ # ausgeben
113
+ case @method_now
114
+
115
+ when :puts
116
+ result = input.inspect_or_to_s
117
+ self.wait!(result) if @speed
118
+ printout(result)
119
+ @delayed_newlines += 1 # das newline wird erst später eingefügt
120
+
121
+ when :pp
122
+ result = input.inspect_see
123
+ self.wait!(result) if @speed
124
+ printout(result)
125
+ @delayed_newlines += 1 # und das newline später eingefügt
126
+
127
+ when :print
128
+ result = input.inspect_or_to_s
129
+ self.wait!(result) if @speed
130
+ printout(result)
131
+
132
+ when :multi
133
+ result = ''
134
+ result = PPP::pp(input, result, 200) # normal prettyprint
135
+ result = PPP.beautify_multi(result)
136
+ self.wait!(result) if @speed
137
+ printout(result)
138
+ @delayed_newlines += 1 # und das newline später eingefügt
139
+
140
+ end # case
141
+
142
+ # verwalten
143
+ @string_last = result.dup
144
+ @time_last = Time.now
145
+ @method_last = @method_now
146
+ @call_stack_last = @call_stack_now
147
+ return (newlines ||'') + result
148
+ end # def
149
+
150
+
151
+ # Führt die aufgeschobenen Newlines aus. Ohne Berücksichtigung der Zeit.
152
+ def process_newline
153
+ result = "\n" * @delayed_newlines
154
+ self.clear! if @delayed_clear
155
+ printout(result)
156
+ @delayed_newlines = 0
157
+ return result
158
+ end
159
+
160
+
161
+ def printout(string, backward=nil)
162
+ if @out.include?(:console)
163
+ spacer = (' '*SEE_TAB_WIDTH * @level)
164
+ outputstring = string.gsub(/\n/, ("\n" + spacer) )
165
+ Kernel.print(spacer) if @cursor_now == 0
166
+ Kernel.print(outputstring)
167
+ end
168
+ if @out.include?(:log)
169
+ # spacer = seee.bench.inspect_see + (' '*SEE_TAB_WIDTH * @level) + ' '
170
+ spacer = ''
171
+ outputstring = string.gsub(/\n/, ("\n" + spacer) )
172
+ logger << (spacer) if @cursor_now == 0
173
+ logger << (outputstring)
174
+ end
175
+
176
+ # TODO: \n berücksichtigen
177
+ unless backward
178
+ @cursor_now += outputstring.size
179
+ else
180
+ @cursor_now -= backward
181
+ end
182
+ end
183
+
184
+
185
+
186
+
187
+ end # class
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+ end # module
196
+
197
+
198
+ class Object
199
+
200
+ # Liefert entweder inspect oder to_s, je nach Klassenzugehörigkeit
201
+ # Explizite Typabfrage ohne duck typing!
202
+ def inspect_or_to_s
203
+ return 'empty' if self == ''
204
+ return self.to_s if self.class == String
205
+ return self.inspect_see if self.class == Time
206
+ return self.inspect_see if self.kind_of?(Numeric)
207
+ return self.inspect_see if self.kind_of?(Set)
208
+ return self.inspect
209
+ end
210
+
211
+ end # class
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+ # -----------------------------------------------------------------------------------------
221
+ # ausprobieren
222
+ #
223
+ if $0 == __FILE__ then
224
+
225
+ require File.join(File.dirname(__FILE__), '..', '..', 'demo', 'demo_pp' )
226
+ Perception::DemoSee.see_all_demos
227
+
228
+ end # if
229
+
230
+
231
+
@@ -0,0 +1,377 @@
1
+
2
+ require File.join(File.dirname(__FILE__), '..', 'perception' ) if $0 == __FILE__
3
+
4
+ class PPP < PP
5
+
6
+
7
+ # ------------------------------------------------------------------------------
8
+ #
9
+ #
10
+
11
+ def self.pp(obj, out=$>, width=79 )
12
+ q = PPP.new(out, width, "\n")
13
+ q.guard_inspect_key {q.pp(obj)}
14
+ q.flush
15
+ #$pp = q
16
+ out
17
+ end
18
+
19
+ # sorgt dafür, dass Listen länger als 3 Elemente untereinander ausgegeben werden
20
+ def seplist(list, sep=nil, iter_method=:each) # :yield: element
21
+ sep = lambda { comma_newline } if (sep.kind_of?(Integer) && list.size > sep )
22
+ sep = lambda { comma_breakable } if (sep.nil? || sep.kind_of?(Integer))
23
+ first = true
24
+ list.__send__(iter_method) {|*v|
25
+ if first
26
+ first = false
27
+ else
28
+ sep.call
29
+ end
30
+ yield(*v)
31
+ }
32
+ end
33
+
34
+ def comma_newline
35
+ text ","
36
+ breakable('',9999)
37
+ end
38
+
39
+
40
+ # Hashes mit mehr als drei Keys werden untereinander dargestellt
41
+ def pp_hash(obj)
42
+ group(1, '{', '}') {
43
+ seplist(obj, 3, :each_pair) {|k, v|
44
+ group {
45
+ pp k
46
+ text '=>'
47
+ group(1) {
48
+ breakable ''
49
+ pp v
50
+ }
51
+ }
52
+ }
53
+ }
54
+ end
55
+
56
+
57
+
58
+
59
+ # ------------------------------------------------------------------------------
60
+ #
61
+ #
62
+
63
+
64
+
65
+
66
+ # Nachbearbeitung des PrettyPrint-Results
67
+ def self.beautify(string)
68
+ begin
69
+ result = string# .gsub!("`","'")
70
+ result = beautify_focus_level1(result)
71
+ case classify_structure(string)
72
+ when :object then result = beautify_object(result)
73
+ when :hash then result = beautify_hash(result)
74
+ when :array_1dim then result = beautify_array_1dim(result)
75
+ when :array_2dim then result = beautify_array_2dim(result)
76
+ end
77
+ return result
78
+ # rescue
79
+ # return string
80
+ end
81
+ end # def
82
+
83
+
84
+ # Klassifiziert die Struktur eines Objektes für die Ausgabe
85
+ def self.classify_structure(object)
86
+
87
+ # Hash
88
+ return :hash if object =~ /^\{.*=>.*\}/m
89
+
90
+ # Object
91
+ return :object if object =~ /^#<.*>$/m
92
+
93
+ # Eindimensionales Array
94
+ # oder Objekt, das sich wie ein Array ausdruckt -- vor der eckigen Klammer ist ein Identifier erlaubt, z.B. WP[ 1, 2, 3]
95
+ return :array_1dim if object =~ /^[A-Z_0-9a-z]{0,30}\[[^\[].*[^\]]\]$/m
96
+
97
+ # Zweidimensionales Array
98
+ # Anfang prüfen: Zwei eckige Klammern auf, danach keine eckige Klammer auf
99
+ # oder alternativ ein Text zwischen den beiden Klammern auf
100
+ return :array_2dim if object =~ /^\[[A-Z_0-9a-z]{0,30}\[[^\[]/m
101
+ #return self unless object =~ /[^\]]\]\]$/ # Ende prüfen: Zwei eckige Klammern zu, davor keine eckige Klammer zu
102
+
103
+ end
104
+
105
+
106
+
107
+ # Level 2+ : nur noch eine Zeile
108
+ # Level 3+ : komplexe Objekte zusammenkürzen
109
+ def self.beautify_focus_level1(string)
110
+ #return string
111
+ replaces = [[/\n/, ''], [/ {2,}/,' ']]
112
+ result = string
113
+ result = string.mask( :level_start => 2 ) { |s| s.mgsub(replaces)}
114
+ result = result.mask( :level_start => 3 ) { |s| (s =~ /@.*=.*/ ? '' : s) }
115
+ return result
116
+ end
117
+
118
+
119
+
120
+ # Object
121
+ def self.beautify_object(string)
122
+ result = string
123
+ result.gsub!(/= *\n */m, '=' ) # unnötigen Zeilenumbruch vermeiden
124
+ result = result.mask( :level_start => 1,
125
+ :level_end => 1,
126
+ :pattern => /</ ) { |s| s.gsub(/=/, ' = ')}
127
+ replaces = [ [/ @/, ' @'], [/= #/,'=#'] ] # Unterobjekte formatieren
128
+ result = result.mask( :level_start => 2,
129
+ :level_end => 2 ) { |s| s.mgsub(replaces)}
130
+ result = result.mask( :level_start => 4 ) { |s| ''}
131
+
132
+ tabstops = result.analyze_columns( :level_start => 0,
133
+ :level_end => 0,
134
+ :search => '=' ) || [25]
135
+ tabstops2 = []
136
+ tabstops2 << tabstops[0]
137
+ tabstops2 << tabstops[0] + 5
138
+
139
+ result = result.spread( :level_start => 1,
140
+ :level_end => 1,
141
+ :search => /=[^>=]/,
142
+ :position_add => 1, # alle '=' links
143
+ :tabstops => tabstops2 )
144
+
145
+ result
146
+ end
147
+
148
+
149
+
150
+ # Hash
151
+ def self.beautify_hash(string)
152
+ result = string
153
+ result.gsub!(/=> *\n */m, '=>' ) # unnötigen Zeilenumbruch vermeiden
154
+
155
+ # mehrzeilige Darstellung
156
+ if result.include?("\n")
157
+ result = string.mask( :level_start => 1,
158
+ :level_end => 1,
159
+ :pattern => /\{/ ) { |s| s.gsub(/=>/, ' =>')}
160
+
161
+ # Pfeil retten
162
+ result = result.mask( :level_start => 2,
163
+ :pattern => /\{/ ) { |s| s.gsub('=>', Perception::PPP_PFEIL)}
164
+
165
+ # Unterobjekte formatieren
166
+ replaces = [ [/, /,', '] ]
167
+ result = result.mask( :level_start => 2,
168
+ :level_end => 2 ) { |s| s.mgsub(replaces)}
169
+
170
+ # Tiefere Level retten
171
+ result = result.mask( :level_start => 1,
172
+ :with_brackets => true,
173
+ :pattern => /\{/
174
+ ) { |s| s.tr(Perception::PPP_TR_ARRAY_P[0..5], Perception::PPP_TR_ARRAY_Q)}
175
+
176
+ tabstops = result.analyze_columns( :level_start => 0,
177
+ :level_end => 0,
178
+ :search => '=' ) || [25]
179
+ tabstops2 = []
180
+ tabstops2 << tabstops[0] + 1
181
+ tabstops2 << tabstops[0] + 5
182
+
183
+ # ausrichten
184
+ result = result.spread( :level_start => 1,
185
+ :level_end => 1,
186
+ :search => /=>/,
187
+ :position_add => 0, # alle '=>' übereinander
188
+ :tabstops => tabstops2 )
189
+ result.gsub!('=>','=> ')
190
+
191
+ # Tiefere Level zurück
192
+ result = result.mask( :level_start => 1,
193
+ :with_brackets => true,
194
+ :pattern => /\{/
195
+ ) { |s| s.tr(Perception::PPP_TR_ARRAY_Q, Perception::PPP_TR_ARRAY_R)}
196
+
197
+ # Pfeil zurück
198
+ result = result.mask( :level_start => 2,
199
+ :pattern => /\{/ ) { |s| s.gsub(Perception::PPP_PFEIL, '=>')}
200
+
201
+
202
+
203
+ # einzeilige Darstellung
204
+ else
205
+ result = string.mask( :level_start => 1,
206
+ :level_end => 1,
207
+ :pattern => /\{/ ) { |s| s.gsub(/, /, ', ')}
208
+
209
+ end
210
+
211
+ result
212
+ end
213
+
214
+
215
+
216
+ # Eindimensionales Array
217
+ # oder Objekt, das sich wie ein Array ausdruckt -- vor der eckigen Klammer ist ein Identifier erlaubt, z.B. WP[ 1, 2, 3]
218
+ def self.beautify_array_1dim(string)
219
+ return string
220
+ # tabstops = result[1..-2].analyze_columns( :level_start => 0,
221
+ # :level_end => 0,
222
+ # :search => ',',
223
+ # :stretch => 0 )
224
+ # tabstops = [25] if tabstops.empty?
225
+ # maxtab = tabstops.sort[-1]
226
+
227
+
228
+ # result = result.spread( :level_start => 1,
229
+ # :level_end => 1,
230
+ # :tabstops => (1..10).collect {|i| i * (maxtab+1)},
231
+ # :search => ',',
232
+ # :position_add => 1 )
233
+ end
234
+
235
+
236
+
237
+ # Zweidimensionales Array
238
+ # very hackish
239
+ def self.beautify_array_2dim(string)
240
+ result = string
241
+
242
+ # Komma retten
243
+ result = result.mask( :level_start => 3,
244
+ :pattern => /['"({<\[]/
245
+ ) { |s| s.tr(',', Perception::PPP_KOMMA)}
246
+
247
+ # Tiefere Level retten
248
+ result = result.mask( :level_start => 2,
249
+ :with_brackets => true,
250
+ :pattern => /\[/
251
+ ) { |s| s.tr(Perception::PPP_TR_ARRAY_P, Perception::PPP_TR_ARRAY_Q)}
252
+
253
+ # zeilenweise
254
+ replaces = [[/,[^\n]/, ", \n "]]
255
+ result = result.mask( :level_start => 1,
256
+ :level_end => 1,
257
+ :pattern => /\[/
258
+ ) { |s| s.mgsub(replaces)}
259
+
260
+ # Tabstops
261
+ tabstops = result[1..-2].analyze_columns( :level_start => 1,
262
+ :level_end => 1,
263
+ :search => ',',
264
+ :stretch => 1 ) || [25]
265
+ tabstops2 = []
266
+ tabstops.each do |t|
267
+ tabstops2 << t + (tabstops2[-1]||0)
268
+ end
269
+
270
+ # ausrichten
271
+ result = result.spread( :level_start => 2,
272
+ :level_end => 2,
273
+ :tabstops => tabstops2,
274
+ :search => /,/ )
275
+
276
+ # Tiefere Level zurück
277
+ result = result.mask( :level_start => 2,
278
+ :with_brackets => true,
279
+ :pattern => /\[/
280
+ ) { |s| s.tr(Perception::PPP_TR_ARRAY_Q, Perception::PPP_TR_ARRAY_R)}
281
+
282
+ #Komma zurück
283
+ result = result.mask( :level_start => 3,
284
+ :pattern => /['"({<\[]/
285
+ ) { |s| s.gsub(Perception::PPP_KOMMA, ',')}
286
+
287
+ return result
288
+
289
+ end
290
+
291
+
292
+ # Mehrere Ausgaben in einer Zeile.
293
+ # Die einzelnen Ausgaben liegen als Array vor.
294
+ # Das Ergebnis soll aber nicht wie ein Array ausssehen.
295
+ def self.beautify_multi(string)
296
+ result = string
297
+ result = beautify_focus_level1(result)
298
+ result = result.mask( :level_start => 1,
299
+ :level_end => 1,
300
+ :pattern => /\[/
301
+ ) {|s| s.tr(',', Perception::PPP_KOMMA)}
302
+ result = result.spread_line( [25], Perception::PPP_KOMMA, 0 )
303
+
304
+ # Äußeres Array entfernen
305
+ result = result.tr(Perception::PPP_TR_ALL,'')
306
+ result = result.mask( :with_brackets => true,
307
+ :level_start => 0,
308
+ :level_end => 0,
309
+ :pattern => /\[/
310
+ ) {|s| s.tr('[]','')}
311
+ return result
312
+ end # def
313
+
314
+
315
+
316
+
317
+
318
+
319
+
320
+ end # class
321
+
322
+
323
+ class String
324
+ def pretty_print(q)
325
+ q.text "'#{self}'"
326
+ end
327
+ end
328
+
329
+ class Set
330
+ def pretty_print(q)
331
+ q.group(1, '{', '}') {
332
+ q.seplist(self) {|v|
333
+ q.pp v
334
+ }
335
+ }
336
+ end
337
+
338
+ def pretty_print_cycle(q)
339
+ q.text(empty? ? '{}' : '{...}')
340
+ end
341
+ end
342
+
343
+
344
+ class Dictionary
345
+ def inspect
346
+ ary = []
347
+ each {|k,v| ary << k.inspect + "=>" + v.inspect}
348
+ '{' + ary.join(", ") + '}'
349
+ end
350
+ end
351
+
352
+
353
+ class Dictionary
354
+ def pretty_print(q)
355
+ q.pp_hash self
356
+ end
357
+
358
+ def pretty_print_cycle(q)
359
+ q.text(empty? ? '{}' : '{...}')
360
+ end
361
+ end
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+ # -----------------------------------------------------------------------------------------
371
+ # Tests
372
+ #
373
+ if $0 == __FILE__ then
374
+
375
+ require File.join(File.dirname(__FILE__), '..', '..', 'test', 'test_ppp' )
376
+
377
+ end # if