mucgly 0.0.1

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