## Copyright (C) 1998 Hrvoje Niksic
## Modification by Zeljko Boros (block entries, removing old entries)
## More options and use strict by Zoran Dzelajlija on 2004-02-24
+## Quite a few improvements by Damir Dzeko on 2005-03-18 (see bellow)
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## -> reformated code, introduced coding conventions
## -> increased reliability through atomic actualization
## -> everything is done in the memory - more mem, less i/o
-## -> removed append mode - in-place editing considered harmfull
+## -> removed append mode - in-place editing considered harmful
## -> added in-place mode as option
use strict;
#
my ($ProgramName, $UsageLong, $UsageShort, $VERSION);
-$VERSION = '2.0';
+$VERSION = '2.1';
# Looks nicer without the slashes and dots
($ProgramName = $0) =~ s!.*/!!; # strip dir
-r | --remove Remove entry PACKAGE from FILE.
Default is to add lines from stdin.
+ -x | --change Modify existing block, or add it if
+ it does not exist but the begin mark
+ can be found.
+
-m | --allow-multiple Allow multiple blocks of the same type.
By default, old blocks are replaced with
the new one.
-h | --help Print this message (usage reference).
-
+
--version Print version message.
- Placement control:
my ($MarkBegin, $MarkEnd, $Trailer, $ParamBegin, $ParamEnd, $Placement,
$Package, $File, $Block, $Multi, $InsertRemove, $Comment, $CommentEnd,
$MatchLine, $RegexpMatch, $StdinContent, $NewContent, $InPlace,
- $NoClose, $FileHandle, @Lines);
+ $NoClose, $FileHandle, @Lines, $LinesCount);
# Placement modes
use constant APPEND_AT_END => 0;
# InsertRemove modes
use constant DO_REMOVE => 0;
use constant DO_INSERT => 1;
+use constant DO_CHANGE => 2;
# Operation defaults
$InsertRemove = DO_INSERT;
elsif (/^-r$/ || /^--remove$/) {
$InsertRemove = DO_REMOVE;
}
+ elsif (/^-x$/ || /^--change$/) {
+ $InsertRemove = DO_CHANGE;
+ }
elsif (/^-m$/ || /^--allow-multi(?:ple)?$/) {
$Multi = 1;
}
unshift(@ARGV, $_);
last;
}
+
+ if ($Multi and DO_CHANGE == $InsertRemove) {
+ die "$ProgramName: Cannot use both `--change' and `--allow-multiple'\n";
+ }
}
$Package = shift or die $UsageShort;
# --------------
sub do_it {
- slurp();
- if (DO_INSERT == $InsertRemove) {
+ $LinesCount = slurp();
+ if (DO_CHANGE == $InsertRemove) {
+ $StdinContent = &stdin_content;
+ change() or add();
+ }
+ elsif (DO_INSERT == $InsertRemove) {
$StdinContent = &stdin_content;
del() unless $Multi;
add();
# If FILE does not have a trailing newline, be sure to add it
# before appending anything else.
-
if (@Lines and $Lines[$#Lines] !~ m/\n$/s) {
$Lines[$#Lines] .= "\n";
}
+
+ return scalar @Lines;
}
sub add() {
push(@Lines, $StdinContent);
}
}
- return scalar(@Lines); # whatever
+ # add the number of lines in added content to safety counter
+ $LinesCount += ($StdinContent =~ tr/\n//);
+ return $LinesCount; # whatever true
}
sub del() {
my (@filtered);
foreach (@Lines) {
- push (@filtered, $_)
- unless (/^$mybegin(?:$mytrailer)?$/o .. /^$myend(?:$mytrailer)?$/o);
-
+ if (/^$mybegin(?:$mytrailer)?$/o .. /^$myend(?:$mytrailer)?$/o) {
+ # additional safety counter for double-check
+ -- $LinesCount;
+ } else {
+ push (@filtered, $_);
+ }
# for safety check:
$bm_found = 1 if (/^$mybegin(?:$mytrailer)?$/o);
$em_found = 1 if (/^$myend(?:$mytrailer)?$/o);
# safety exit
die "$ProgramName: no end-mark after begin-mark!\n";
}
- return scalar(@Lines); # whatever
+ return $LinesCount; # whatever
+}
+
+sub change() {
+ my ($mytrailer, $mybegin, $myend) =
+ ($Trailer, $MarkBegin, $MarkEnd);
+
+ my ($bm_found, $em_found); # begin/end mark found indicator
+
+ # Make the strings regexp-friendly by quoting non-word chars.
+ $mybegin =~ s/\W/\\$&/g;
+ $myend =~ s/\W/\\$&/g;
+ $mytrailer =~ s/\W/\\$&/g;
+
+ my (@filtered, $done, $skip);
+ $done = 0; # job done once
+ $skip = 0; # skip original block
+ foreach (@Lines) {
+ if (! $done and $skip > 0) {
+ -- $LinesCount;
+ if (/^$myend(?:$mytrailer)?$/o) {
+ ++ $done; # skipped all that was to skip
+ } else {
+ ++ $skip; # count lines being skipped
+ }
+ }
+ elsif (0 == $skip and $_ =~ /^$mybegin(?:$mytrailer)?$/o) {
+ push (@filtered, $StdinContent);
+ $LinesCount += ($StdinContent =~ tr/\n//);
+ $skip = 1;
+ }
+ else {
+ push (@filtered, $_)
+ }
+ # for safety check:
+ $bm_found = 1 if (/^$mybegin(?:$mytrailer)?$/o);
+ $em_found = 1 if (/^$myend(?:$mytrailer)?$/o);
+ }
+ if ($bm_found and $em_found) {
+ -- $skip; # correct the counter
+ DEBUG and print STDERR "Replaced block of $skip lines\n";
+ @Lines = @filtered;
+ }
+ elsif ($bm_found and ! $em_found) {
+ # safety exit
+ die "$ProgramName: no end-mark after begin-mark!\n";
+ }
+ return $done;
}
# written by ddzeko@srce.hr, 2005-03-18
# put it all thogether
my $newContent = join('', @Lines);
- unless (length($newContent)) {
+ unless (length($newContent) or 0 == $LinesCount) {
# safety exit in last second :)
die "$ProgramName: New content empty -- aborting file alteration!\n";
}
sysseek(*$FileHandle, 0, SEEK_SET)
or die "$ProgramName: Failed to seek to the begining of file ($!)\n";
}
- my $wb = syswrite($FileHandle, $newContent);
- if (! $wb or length($newContent) != $wb) {
- # FIXME: try restoring backup copy
- die "$ProgramName: Failed to write the content to '$File' ($!)\n";
+ if (length($newContent)) {
+ my $wb = syswrite($FileHandle, $newContent);
+ if (! $wb or length($newContent) != $wb) {
+ # FIXME: try restoring backup copy
+ my $ncl = length($newContent);
+ die "$ProgramName: Failed to write the content to '$File' (wb=$wb, len=$ncl, err:$!)\n";
+ }
}
if ($NoClose) {
# this could be handy for files that had stuff appended
$file_new .= '.cp-update.new'; # our .new file
$file_old .= '.cp-update.old'; # our .old file
# write content in new file in single write op
- sysopen ($FileHandle, $file_new, O_CREAT|O_TRUNC|O_WRONLY)
+ sysopen ($FileHandle, $file_new, O_CREAT|O_TRUNC|O_WRONLY, (stat($File))[2])
or die "$ProgramName: Failed to open file '$File' for writing ($!)\n";
- my $wb = syswrite($FileHandle, $newContent);
- if (! $wb or length($newContent) != $wb) {
- unlink($file_new);
- die "$ProgramName: Failed to write the content to '$File' ($!)\n";
+ if (length($newContent)) {
+ my $wb = syswrite($FileHandle, $newContent);
+ if (! $wb or length($newContent) != $wb) {
+ unlink($file_new);
+ my ($ncl) = length($newContent);
+ die "$ProgramName: Failed to write the content to '$File' (wb=$wb, len=$ncl, err:$!)\n";
+ }
}
close($FileHandle);
# do the moving (should be atomic)
unlink($file_old)
or warn "$ProgramName: Failed to remove file '$file_old' ($!)\n";
}
+ DEBUG and print STDERR "actualize: LinesCount=$LinesCount\n";
}
# return content from standard input