mucgly 0.0.1

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.
data/lib/easyfile.rb ADDED
@@ -0,0 +1,720 @@
1
+ # EasyFile module provides simplified file access management. The user
2
+ # can refer all files by name, also the standard streams (STDIN,
3
+ # STDOUT, STDERR). File names are unique since they are stored as
4
+ # absolute path names. Also short names (relative/original) can be
5
+ # used.
6
+ #
7
+ # The files are divided to Read and Write files. This simplifies the file
8
+ # handling. Both files maintain the current line number.
9
+ #
10
+ # Read files has put-back feature. Arbitrary number of chars can be put
11
+ # back to stream. This is helpful for parsers that has to perform
12
+ # look-ahead.
13
+ #
14
+ # Write file have redirection support. Write file can be split into two
15
+ # physical files if output requires copying to two or more files. Two
16
+ # (or more ) Write files can also be joined, i.e. all writes are
17
+ # directed to the joined file. Stream output can be suppressed.
18
+ #
19
+ # Examples:
20
+ # f = EasyFile::Read.open( 'myfile' )
21
+ # c = f.getc
22
+ # if c == "#"
23
+ # puts "Error in #{f.absname} on line #{f.line+1}: can't have '#' as first char..."
24
+ # end
25
+ #
26
+ # f = EasyFile::Write.open( 'myfile' )
27
+ # f.branch( 'myfile2' )
28
+ # f.puts "foo\nbar\ndii"
29
+ # puts "Number of lines output to 'myfile' and 'myfile2': #{f.line}..."
30
+ #
31
+
32
+ module EasyFile
33
+
34
+
35
+ # Base class for Read and Write files.
36
+ class InOut
37
+
38
+ require 'stringio'
39
+
40
+ # Map symbolic file direction to mode character.
41
+ FILEMODEMAP = {
42
+ :read => 'r',
43
+ :write => 'w',
44
+ }
45
+
46
+ # Get absolute path for name.
47
+ #
48
+ # @param name [String] Relative file name.
49
+ # @return [String] Absolute path name for the file.
50
+ def InOut.abspath( name, mode )
51
+ if InOut.standard?( name )
52
+ name
53
+ elsif mode == :string
54
+ name
55
+ else
56
+ File.absolute_path( name )
57
+ end
58
+ end
59
+
60
+ # Is name for standard IO?
61
+ #
62
+ # @param name [String] Relative file name.
63
+ def InOut.standard?( name )
64
+ case name
65
+ when "<STDIN>"; true
66
+ when "<STDOUT>"; true
67
+ when "<STDERR>"; true
68
+ else false
69
+ end
70
+ end
71
+
72
+
73
+ # Create EasyFile object and, open file with given name and
74
+ # mode.
75
+ #
76
+ # @param name [String] Relative path name for file.
77
+ # @param mode [Symbol] Access direction (:read/:write).
78
+ # @return [Read,Write] EasyFile handle.
79
+ def InOut.open( name, mode = :read )
80
+ file = InOut.create( name, mode )
81
+ file.open
82
+ file
83
+ end
84
+
85
+
86
+ # Create EasyFile object, no open. Open is performed
87
+ # separately.
88
+ #
89
+ # @param name [String] Relative path name for file.
90
+ # @param mode [Symbol] Access direction (:read/:write).
91
+ # @return [Read,Write] EasyFile handle to non-opened file (use
92
+ # "open").
93
+ def InOut.create( name, mode = :read )
94
+
95
+ absname = InOut.abspath( name, mode )
96
+
97
+ # Check if file exists already.
98
+ file = InOut.getByAbsname( absname )
99
+
100
+ if file
101
+
102
+ file
103
+
104
+ else
105
+
106
+ if mode == :read
107
+ # file = Read.new( name, absname )
108
+ file = Read.new( name, absname )
109
+ elsif mode == :write
110
+ file = Write.new( name, absname )
111
+ elsif mode == :string
112
+ file = String.new( name, absname )
113
+ else
114
+ raise RuntimeError, "Unknown file mode \"#{mode.to_s}\"..."
115
+ end
116
+
117
+ InOut.add( file )
118
+
119
+ end
120
+
121
+ file
122
+
123
+ end
124
+
125
+
126
+ # Add InOut to filelist.
127
+ #
128
+ # @param io [InOut] Item to list.
129
+ # @return [InOut] Added item.
130
+ def InOut.add( io )
131
+ @@file_io[ io.absname ] = io
132
+ end
133
+
134
+
135
+ # Delete InOut from filelist.
136
+ #
137
+ # @param io [InOut] Item to list.
138
+ # @return [InOut] Added item.
139
+ def InOut.delete( io )
140
+ @@file_io.delete( io.absname )
141
+ end
142
+
143
+
144
+ # Get InOut from filelist by InOut handle.
145
+ #
146
+ # @param io [InOut] Item handle.
147
+ # @return [InOut] Added item.
148
+ def InOut.get( io )
149
+ @@file_io.getByAbsname( io.absname )
150
+ end
151
+
152
+
153
+ # Get InOut from filelist by absname.
154
+ #
155
+ # @param absname [InOut] Item absname.
156
+ # @return [InOut] Added item.
157
+ def InOut.getByAbsname( absname )
158
+ @@file_io[ absname ]
159
+ end
160
+
161
+
162
+ # Get EasyFile list.
163
+ #
164
+ # @return [Array<InOut>] List of all files.
165
+ def InOut.getList
166
+ @@file_io.values
167
+ end
168
+
169
+
170
+
171
+ # Relative path name for file.
172
+ attr_reader :filename
173
+
174
+ # Absolute path name for file.
175
+ attr_reader :absname
176
+ alias :id :absname
177
+
178
+ # File mode.
179
+ attr_reader :mode
180
+
181
+ # IO stream handle.
182
+ attr_accessor :io
183
+
184
+ # Parent EasyFile (push/pop).
185
+ attr_accessor :parent
186
+
187
+ # List of peer files (braches).
188
+ attr_accessor :peers
189
+
190
+ # Current line number (0,1,2...)
191
+ attr_accessor :line
192
+
193
+
194
+ # Create InOut object.
195
+ #
196
+ # @param mode [Symbol] File operation mode (:read, :write, :string).
197
+ # @param filename [String] Relative path file name.
198
+ # @param absname [String] Absolute path file name (generated
199
+ # if not given).
200
+ # @param io [IO] IO handle.
201
+ def initialize( mode, filename, absname = nil, io = nil )
202
+
203
+ @mode = mode
204
+
205
+ unless absname
206
+ absname = InOut.abspath( filename, mode )
207
+ end
208
+
209
+ # Set Primary IO.
210
+ @io = io
211
+
212
+ @filename = filename
213
+ @absname = absname
214
+
215
+ @peers = []
216
+
217
+ @line = 0
218
+ end
219
+
220
+
221
+ # Open the stream.
222
+ def open
223
+
224
+ # Ensure single open.
225
+ return if self.io
226
+
227
+ if mode == :string
228
+ handle = StringIO.new
229
+ else
230
+ handle = File.open( @absname, FILEMODEMAP[ mode ] )
231
+ end
232
+
233
+ self.io = handle
234
+
235
+ self
236
+ end
237
+
238
+
239
+ # Close primary stream and peer streams.
240
+ def close
241
+
242
+ begin
243
+ unless InOut.standard?( @filename )
244
+ @io.close if @io
245
+ InOut.delete( self )
246
+ end
247
+ rescue IOError
248
+ end
249
+
250
+ @peers.each do |i|
251
+ i.close
252
+ end
253
+ end
254
+
255
+
256
+ private
257
+
258
+ # Update line number status according to encountered newline
259
+ # chars.
260
+ #
261
+ # @param blk [String] Transfer chars.
262
+ # @param offset [Integer] Line number value Change step.
263
+ def updateLineNumber( blk, offset = 1 )
264
+ return nil unless blk
265
+ cnt = 0
266
+ blk.each_char do |c|
267
+ cnt += offset if c == "\n"
268
+ end
269
+ @line += cnt
270
+ blk
271
+ end
272
+
273
+ end
274
+
275
+
276
+
277
+ # Read file.
278
+ #
279
+ # Features:
280
+ # * Line counting.
281
+ # * Put-back char support.
282
+ class Read < InOut
283
+
284
+
285
+ # Create EasyFile::Read object and, open file with given name.
286
+ #
287
+ # @param name [String] Relative path name for file.
288
+ # @return [Read] EasyFile handle.
289
+ def Read.open( name )
290
+ InOut.open( name, :read )
291
+ end
292
+
293
+
294
+ # Create Read object.
295
+ #
296
+ # @param filename [String] Relative path file name.
297
+ # @param absname [String] Absolute path file name (generated
298
+ # if not given).
299
+ # @param io [IO] IO handle.
300
+ def initialize( filename, absname = nil, io = nil )
301
+ super( :read, filename, absname, io )
302
+
303
+ @charbuffer = ""
304
+ end
305
+
306
+
307
+ # Get line.
308
+ def gets
309
+ begin
310
+ readline
311
+ rescue EOFError
312
+ nil
313
+ end
314
+ end
315
+
316
+
317
+ # Get line without newline.
318
+ def getschomp
319
+ line = gets
320
+ if line
321
+ line.chomp
322
+ else
323
+ line
324
+ end
325
+ end
326
+
327
+
328
+ # Get char (from IO or put-back buffer).
329
+ def getc
330
+ begin
331
+ getchar
332
+ rescue EOFError
333
+ nil
334
+ end
335
+ end
336
+
337
+
338
+ # Get char (from IO or put-back buffer).
339
+ def getchar
340
+ updateLineNumber( getn( 1 ) )
341
+ end
342
+
343
+
344
+ # Put-back char(s) (to put-back buffer).
345
+ def putback( c )
346
+ @charbuffer = c + @charbuffer
347
+ updateLineNumber( c, -1 )
348
+ end
349
+
350
+
351
+ # Read from IO.
352
+ def read( length = 1024 )
353
+ begin
354
+ readchars( length )
355
+ rescue EOFError
356
+ nil
357
+ end
358
+ end
359
+
360
+
361
+ # Read from IO with exception.
362
+ def readchars( length = 1024 )
363
+ updateLineNumber( getn( length ) )
364
+ end
365
+
366
+
367
+ def eof?
368
+ @charbuffer.empty? && @io.eof?
369
+ end
370
+
371
+ alias eof eof?
372
+
373
+
374
+ # Return Array of lines.
375
+ def readlines( chomp = false )
376
+ lines = []
377
+ if chomp
378
+ loop do
379
+ line = getschomp
380
+ break unless line
381
+ lines.push line
382
+ end
383
+ else
384
+ loop do
385
+ line = gets
386
+ break unless line
387
+ lines.push line
388
+ end
389
+ end
390
+ lines
391
+ end
392
+
393
+ # Iterate over lines.
394
+ #
395
+ # @yield Each line.
396
+ def each_line( &blk )
397
+ while ( line = gets )
398
+ yield line
399
+ end
400
+ end
401
+
402
+
403
+ # Iterate over lines with newline removed.
404
+ #
405
+ # @yield Each line.
406
+ def each_chomp( &blk )
407
+ while ( line = getschomp )
408
+ yield line
409
+ end
410
+ end
411
+
412
+
413
+ # Readline from file.
414
+ def readline
415
+ updateLineNumber( getline )
416
+ end
417
+
418
+
419
+
420
+ private
421
+
422
+ # Get n characters from stream.
423
+ def getn( n = 1 )
424
+
425
+ while n > @charbuffer.length
426
+ # Fill char buffer first.
427
+ break if @io.eof?
428
+ @charbuffer += @io.readline
429
+ end
430
+
431
+ ret = @charbuffer[ 0..(n-1) ]
432
+
433
+ raise EOFError if ret == ""
434
+
435
+ @charbuffer = @charbuffer[ n..-1 ]
436
+
437
+ unless @charbuffer
438
+ @charbuffer = ""
439
+ end
440
+
441
+ ret
442
+ end
443
+
444
+
445
+ # Get chars until newline.
446
+ def getline
447
+
448
+ if @charbuffer.empty?
449
+ @io.readline
450
+ else
451
+ ret = ""
452
+ nl = nil
453
+
454
+ # Check if newline in @charbuffer.
455
+ if ( nl = @charbuffer.index( "\n" ) )
456
+ ret = @charbuffer[ 0..nl ]
457
+ @charbuffer = @charbuffer[ nl+1..-1 ]
458
+ ret
459
+ else
460
+ if @io.eof?
461
+ ret = @charbuffer
462
+ else
463
+ ret = @charbuffer + @io.readline
464
+ end
465
+ @charbuffer = ""
466
+ ret
467
+ end
468
+ end
469
+ end
470
+
471
+ end
472
+
473
+
474
+
475
+ # Write file.
476
+ #
477
+ # Features:
478
+ # * Line counting.
479
+ # * Branching and joining.
480
+ # * Stream output suppression.
481
+ class Write < InOut
482
+
483
+
484
+ # Create EasyFile::Write object and, open file with given name.
485
+ #
486
+ # @param name [String] Relative path name for file.
487
+ # @return [Write] EasyFile handle.
488
+ def Write.open( name )
489
+ InOut.open( name, :write )
490
+ end
491
+
492
+
493
+ # Output is enabled.
494
+ attr_accessor :enabled
495
+
496
+ # Flush after each write.
497
+ attr_accessor :flush
498
+
499
+
500
+ # Create Write object.
501
+ #
502
+ # @param filename [String] Relative path file name.
503
+ # @param absname [String] Absolute path file name (generated
504
+ # if not given).
505
+ # @param io [IO] IO handle.
506
+ def initialize( filename, absname = nil, io = nil )
507
+ super( :write, filename, absname, io )
508
+ @enabled = true
509
+ @flush = false
510
+ end
511
+
512
+
513
+ # Write to all IOs.
514
+ def write( str )
515
+ if @enabled
516
+ updateLineNumber( str )
517
+ if @io
518
+ @io.write( str )
519
+ @io.flush if @flush
520
+ else
521
+ # No output with nil stream.
522
+ end
523
+
524
+ @peers.each do |p|
525
+ p.write( str )
526
+ end
527
+ end
528
+ end
529
+
530
+
531
+ # Write with automatic newline.
532
+ def puts( str )
533
+ nl = str[-1] == "\n" ? '' : "\n"
534
+ write( str + nl )
535
+ end
536
+
537
+
538
+ # Attach a file to existing Write stream i.e. writes to Write
539
+ # will be directed to attached file as well.
540
+ #
541
+ # @param fileid [String,Write] Filename or Write to add branching.
542
+ # @return [Write] Added peer.
543
+ def branch( fileid )
544
+
545
+ if fileid.class == String
546
+
547
+
548
+ # Get new IO.
549
+ fileio = Write.open( fileid )
550
+
551
+ else
552
+
553
+ fileio = fileid
554
+
555
+ end
556
+
557
+ # Add if not already included.
558
+ @peers.push fileio unless @peers.index( fileio )
559
+
560
+ fileio
561
+ end
562
+
563
+
564
+ # Join the given filestream to this stream. After joining the
565
+ # joined stream is output to same file as this stream.
566
+ #
567
+ # @param fileid [String,Write] Filename or Write to join.
568
+ # @return [Write] Joined peer.
569
+ def join( fileid )
570
+
571
+ if fileid.class == String
572
+ fileio = Write.create( fileid, nil )
573
+ else
574
+ fileio = fileid
575
+ fileio.close
576
+ end
577
+
578
+ fileio.io = @io
579
+ fileio
580
+ end
581
+
582
+
583
+ # Suppress stream output.
584
+ #
585
+ # @param disable [Boolean] Disable or enable suppression.
586
+ def suppress( disable = true )
587
+ @enabled = !disable
588
+ end
589
+
590
+ end
591
+
592
+
593
+ # String buffer file.
594
+ #
595
+ # Features:
596
+ # * Putback.
597
+ class String < InOut
598
+
599
+ def method_missing( name, *args, &block )
600
+ @io.send( name, *args, &block )
601
+ end
602
+
603
+
604
+ # Create EasyFile::String object and, open file with given name.
605
+ #
606
+ # @param name [String] Name for file.
607
+ # @return [String] EasyFile handle.
608
+ def String.open( name )
609
+ InOut.open( name, :string )
610
+ end
611
+
612
+ # Create String object.
613
+ #
614
+ # @param filename [String] Name.
615
+ # @param absname [String] Absolute path file name (generated
616
+ # if not given).
617
+ # @param io [IO] IO handle.
618
+ def initialize( filename, absname = nil, io = nil )
619
+ super( :string, filename, absname, io )
620
+ end
621
+
622
+
623
+ # Put-back char(s) (to put-back buffer).
624
+ # @param c [String] Chars to put back to buffer.
625
+ def putback( c )
626
+ @io.string = c + @io.string[ @io.pos..-1 ]
627
+ end
628
+
629
+
630
+ end
631
+
632
+
633
+ class InOut
634
+
635
+ # Declare standards IOs.
636
+ @@file_io = {
637
+ "<STDIN>" => Read.new( '<STDIN>', nil, STDIN ),
638
+ "<STDOUT>" => Write.new( '<STDOUT>', nil, STDOUT ),
639
+ "<STDERR>" => Write.new( '<STDERR>', nil, STDERR ),
640
+ # filename (absname) => InOut object
641
+ }
642
+
643
+ end
644
+
645
+
646
+ # Stack of files.
647
+ module Stacked
648
+ attr_accessor :stack
649
+
650
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
651
+
652
+ def setup( first = nil )
653
+ if first
654
+ @stack = [ first ]
655
+ else
656
+ @stack = []
657
+ end
658
+ end
659
+
660
+ def pop
661
+ if @stack.length > 1
662
+ @stack[-1].close
663
+ @stack.pop
664
+ end
665
+ end
666
+
667
+ def method_missing( name, *args, &block )
668
+ if [ :getc, :gets, :gets! ].index( name )
669
+ if @stack[-1].eof? && @automode
670
+ pop
671
+ end
672
+ end
673
+ @stack[-1].send( name, *args, &block )
674
+ end
675
+
676
+ alias close pop
677
+ end
678
+
679
+
680
+ # Stack of Read files.
681
+ class ReadStack
682
+ include Stacked
683
+
684
+ # Automatically revert to lower file at EOF.
685
+ attr_accessor :automode
686
+
687
+ def initialize( name )
688
+ io = InOut.open( name, :read )
689
+ setup( io )
690
+ @automode = true
691
+ end
692
+
693
+ def push( name )
694
+ io = InOut.open( name, :read )
695
+ @stack.push io
696
+ end
697
+
698
+ def eof?
699
+ @stack.length == 1 && @stack[-1].eof?
700
+ end
701
+ end
702
+
703
+
704
+ # Stack of Write files.
705
+ class WriteStack
706
+ include Stacked
707
+
708
+ def initialize( name )
709
+ io = InOut.open( name, :write )
710
+ setup( io )
711
+ end
712
+
713
+ def push( name )
714
+ io = InOut.open( name, :write )
715
+ @stack.push io
716
+ end
717
+
718
+ end
719
+
720
+ end