Class: Patman

Inherits:
Object
  • Object
show all
Defined in:
lib/patman.rb,
lib/version.rb

Overview

Patman

Introduction

Patman (Patch Manipulator) is a library for text file patching. It can also be used to extract information from files.

Typical Patman script opens a file for editing. The file is read into the library. User finds the place for editing either with Regexp searches or with direct line numbers. The file content is edited by adding, removing, or replacing lines. When all edits are done, the updated file content is written to disk.

All editing commands refer to the “current position”. Current position is returned by “line” method. Positions refer to lines that have content. If user wants append to the end of file, then user should jump to last line, with “lastline” method, and then issue “append”. It is also possible to jump to arbitrary lines, Patman does not prevent this. The line positions are just used as an index to Array. For example negative line number will refer from end towards beginning in content.

Position can be explicitly changed with “step”, “firstline”, or “lastline” methods (commands). “find” changes position if the pattern is found in selected direction. “append” changes position implicitly with every call.

Current line content is returned by “get” and it can be set with “set” method. Current line content can be replaced with “sub”.

Patman includes many query commands: line, lines, [], get, find, get_range, get_for. They all return the queried item. All other methods return Patman object itself, hence many Patman methods can be “chained”.

Block commands perform commands over a range of lines. Block commands are: do_all, do_range, and do_for. These retain the original position, but the final position is stored (actually one after) and it can be activated by calling “blockline” method.

Block commands take a pre-defined number of lines to process. Note that, if user deletes lines in block action, the outcome is most likely not what the user expects.

Mark feature can be used if user wants to return back to original position after changes. Mark features includes a “default mark” and “named marks”.

For debugging purposes it is good to see line content. “view” and “view_ln” can be used to view line content either without or with line numbers respectively.

No changes are stored to disk unless “write” is called. If user want to create a “backup” of the edited file, the “copy” method can be used before any editing commands have been applied.

Example session

# Open file for reading.
r = Patman.read( "report.txt" )

# Backup file and find next line with "cpp", method chaining.
r.copy( "report.txt.org" ).find( /cpp/ )

# Collect some lines.
data = 4.times.collect do |i|
    r.ref( r.line + i )
end

# Duplicate the lines collected.
r.insert( data )

# Move to line 9.
r.line 9

# Append " Hello" to the end of current line.
r.set( r.get + " Hello" )

# Save changes.
r.write

Defined Under Namespace

Classes: PatmanError, PatmanFileError, PatmanSearchError

Constant Summary collapse

VERSION =
"0.0.2"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file) ⇒ Patman

Create Patman object.



106
107
108
109
110
111
112
113
114
# File 'lib/patman.rb', line 106

def initialize( file )
    @file = file
    @lines = []
    @line = 0
    @mark = nil
    @marks = {}
    @blockline = nil
    @edited = false
end

Instance Attribute Details

#marksObject

Returns the value of attribute marks.



95
96
97
# File 'lib/patman.rb', line 95

def marks
  @marks
end

Class Method Details

.read(file) ⇒ Object

Create editing session with file.



99
100
101
102
103
# File 'lib/patman.rb', line 99

def Patman.read( file )
    p = Patman.new( file )
    p.read
    p
end

.versionObject



3
4
5
# File 'lib/version.rb', line 3

def Patman.version
    Patman::VERSION
end

Instance Method Details

#[](range) ⇒ Object

Reference Patman content by range.



194
195
196
# File 'lib/patman.rb', line 194

def []( range )
    @lines[ range ]
end

#append(text = nil) ⇒ Object

Append after current position.



263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/patman.rb', line 263

def append( text = nil )
    @edited = true
    if text.kind_of? Array
        text.each do |txt|
            append( txt )
        end
    else
        step
        @lines.insert( @line, text )
    end
    self
end

#blocklineObject

Jump to line after block.



176
177
178
179
180
181
# File 'lib/patman.rb', line 176

def blockline
    if @blockline
        @line = @blockline
    end
    self
end

#clearObject

Clear Patman content and reset current line.



296
297
298
299
300
301
# File 'lib/patman.rb', line 296

def clear
    @edited = true
    @lines = []
    @line = 0
    self
end

#copy(file) ⇒ Object

Copy Patman content to file.



142
143
144
145
# File 'lib/patman.rb', line 142

def copy( file )
    write( file )
    self
end

#delete(count = 1) ⇒ Object

Delete current line.



277
278
279
280
281
282
283
# File 'lib/patman.rb', line 277

def delete( count = 1 )
    @edited = true
    count.times do |i|
        @lines.delete_at( @line )
    end
    self
end

#do_all(&blk) ⇒ Object

Execute given block for all lines, i.e. all positions. Block parameter is Patman.



377
378
379
# File 'lib/patman.rb', line 377

def do_all( &blk )
    do_for( 1, length-1, &blk )
end

#do_for(start, count, &blk) ⇒ Object

Execute given block starting from start by count, and update position.



389
390
391
392
393
394
395
396
397
398
399
# File 'lib/patman.rb', line 389

def do_for( start, count, &blk )
    line = @line
    @line = start-1
    count.times do
        yield self
        @line += 1
    end
    @blockline = @line
    @line = line
    self
end

#do_range(start, stop, &blk) ⇒ Object

Execute given block between start and stop positions, and update position.



383
384
385
# File 'lib/patman.rb', line 383

def do_range( start, stop, &blk )
    do_for( start, (stop-start+1), &blk )
end

#editObject

Mark content modified (explicit).



332
333
334
# File 'lib/patman.rb', line 332

def edit
    @edited = true
end

#edited?Boolean

Return true if content is modified.



337
338
339
# File 'lib/patman.rb', line 337

def edited?
    @edited
end

#excursion(&blk) ⇒ Object

Execute block, retain current position, and return block value.



342
343
344
345
346
347
# File 'lib/patman.rb', line 342

def excursion( &blk )
    line = @line
    ret = instance_eval( &blk )
    @line = line
    ret
end

#filenameObject

Return Patman file name.



327
328
329
# File 'lib/patman.rb', line 327

def filename
    @file
end

#find(re_or_str, forward = true) ⇒ Object

Find Regexp or literal string forwards or backwards. Return true on success.



305
306
307
308
309
310
311
312
# File 'lib/patman.rb', line 305

def find( re_or_str, forward = true )
    begin
        @line = search_with_exception( re_or_str, forward )
        true
    rescue
        false
    end
end

#firstlineObject

Jump to first line.



164
165
166
167
# File 'lib/patman.rb', line 164

def firstline
    @line = 0
    self
end

#get(count = 1) ⇒ Object

Get current line or lines by count.



199
200
201
202
203
204
205
# File 'lib/patman.rb', line 199

def get( count = 1 )
    if count == 1
        @lines[ @line ]
    else
        @lines[ @line .. (@line+count-1) ]
    end
end

#get_for(start, count) ⇒ Object

Get lines starting from start by count.



407
408
409
# File 'lib/patman.rb', line 407

def get_for( start, count )
    @lines[ (start-1) ... (start-1+count) ]
end

#get_range(start, stop) ⇒ Object

Get lines between start and stop positions inclusive.



402
403
404
# File 'lib/patman.rb', line 402

def get_range( start, stop )
    @lines[ (start-1) .. (stop-1) ]
end

#insert(text = nil) ⇒ Object

Insert line or lines (Array) to current position.



247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/patman.rb', line 247

def insert( text = nil )
    @edited = true
    if text.kind_of? Array
        line = @line
        step -1
        text.each do |txt|
            append( txt )
        end
        @line = line
    else
        @lines.insert( @line, text )
    end
    self
end

#insertfile(file) ⇒ Object

Insert file to current position.



286
287
288
289
290
291
292
293
# File 'lib/patman.rb', line 286

def insertfile( file )
    @edited = true
    step -1
    read_clean( file ).each do |line|
        append line
    end
    self
end

#lastlineObject

Jump to last line.



170
171
172
173
# File 'lib/patman.rb', line 170

def lastline
    @line = @lines.length-1
    self
end

#lengthObject

Return line count of Patman content.



322
323
324
# File 'lib/patman.rb', line 322

def length
    @lines.length
end

#line(arg = nil) ⇒ Object

Return or set line.



148
149
150
151
152
153
154
155
# File 'lib/patman.rb', line 148

def line( arg = nil )
    if arg
        @line = (arg-1)
        self
    else
        @line+1
    end
end

#lines(arg = nil) ⇒ Object

Get or set all Patman content.



184
185
186
187
188
189
190
191
# File 'lib/patman.rb', line 184

def lines( arg = nil )
    if arg
        @edited = true
        @lines = arg
    else
        @lines
    end
end

#mark(tag = nil) ⇒ Object

Mark (store) current position to default or to named mark.



350
351
352
353
354
355
356
357
358
# File 'lib/patman.rb', line 350

def mark( tag = nil )
    if tag
        @marks[ tag ] = @line+1
        self
    else
        @mark = @line+1
        self
    end
end

#peek(count = 0) ⇒ Object

View line content around current position (by count).



412
413
414
415
# File 'lib/patman.rb', line 412

def peek( count = 0 )
    view_range( @line-count, @line+count+1 )
    nil
end

#peek_ln(count = 0) ⇒ Object

View line content with line numbers around current position (by count).



419
420
421
422
# File 'lib/patman.rb', line 419

def peek_ln( count = 0 )
    view_range( @line-count, @line+count+1, true )
    nil
end

#readObject

Read file in.



117
118
119
120
121
122
123
# File 'lib/patman.rb', line 117

def read
    if File.exist?( @file )
        @lines = read_clean( @file )
    else
        raise PatmanFileError
    end
end

#ref(line = nil) ⇒ Object

Get current line or any line.



208
209
210
211
212
213
214
# File 'lib/patman.rb', line 208

def ref( line = nil )
    if line
        @lines[ line-1 ]
    else
        @lines[ @line ]
    end
end

#search(re_or_str, forward = true) ⇒ Object

Search Regexp or literal string forwards or backwards. Fail with expection (PatmanSearchError) if not found.



316
317
318
319
# File 'lib/patman.rb', line 316

def search( re_or_str, forward = true )
    @line = search_with_exception( re_or_str, forward )
    self
end

#set(text) ⇒ Object

Set current line.



217
218
219
220
221
# File 'lib/patman.rb', line 217

def set( text )
    @edited = true
    @lines[ @line ] = text
    self
end

#step(dir = 1) ⇒ Object

Step forward or backward current position.



158
159
160
161
# File 'lib/patman.rb', line 158

def step( dir = 1 )
    @line = @line + dir
    self
end

#sub(from, to) ⇒ Object

Substitution in current line.



224
225
226
227
228
# File 'lib/patman.rb', line 224

def sub( from, to )
    @edited = true
    @lines[ @line ] = @lines[ @line ].sub( from, to )
    self
end

#unmark(tag = nil) ⇒ Object

Unmark (restore) current position from default or from named mark.



362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/patman.rb', line 362

def unmark( tag = nil )
    if tag && @marks[ tag ]
        @line = @marks[ tag ]-1
        self
    elsif @mark
        @line = @mark-1
        @mark = nil
        self
    else
        self
    end
end

#update(&blk) ⇒ Object

Update current line content (i.e. get&set) with the return value of the given block. Hence last stmt should include the new line content.

Examples:

r.update do |c|
   c.sub!( /foo/, 'bar' )
   c
end


240
241
242
243
244
# File 'lib/patman.rb', line 240

def update( &blk )
    @edited = true
    set( yield( get ) )
    self
end

#view(arg1 = nil, arg2 = nil) ⇒ Object

View line content.

  • no args: view all

  • one arg: view from current onwards by count

  • two args: view given range



429
430
431
432
433
434
435
436
437
438
# File 'lib/patman.rb', line 429

def view( arg1 = nil, arg2 = nil )
    if !arg1 && !arg2
        view_range( 0, length )
    elsif arg1 && !arg2
        view_range( @line, @line+arg1 )
    elsif arg1 && arg2
        view_range( arg1-1, arg1-1+arg2 )
    end
    nil
end

#view_ln(arg1 = nil, arg2 = nil) ⇒ Object

View line content with line numbers.

  • no args: view all

  • one arg: view from current onwards by count

  • two args: view given range



445
446
447
448
449
450
451
452
453
454
# File 'lib/patman.rb', line 445

def view_ln( arg1 = nil, arg2 = nil )
    if !arg1 && !arg2
        view_range( 0, length, true )
    elsif arg1 && !arg2
        view_range( @line, @line+arg1, true )
    elsif arg1 && arg2
        view_range( arg1-1, arg1-1+arg2, true )
    end
    nil
end

#write(file = @file) ⇒ Object

Write Patman content to disk.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/patman.rb', line 126

def write( file = @file )
    return unless @edited
    fh = File.open( file, 'w' )
    @lines.each do |line|
        if line
            fh.puts line
        else
            fh.puts ""
        end
    end
    fh.close
    @edited = false
    self
end