Prva inačica za bullseye
[carnet-tools-cn.git] / cp-update
index 6491c78..5514ee6 100755 (executable)
--- a/cp-update
+++ b/cp-update
@@ -3,6 +3,7 @@
 ## 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
@@ -22,7 +23,7 @@
 ## -> 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;
@@ -34,7 +35,7 @@ sub DEBUG () { 0 };
 #
 my ($ProgramName, $UsageLong, $UsageShort, $VERSION);
 
-$VERSION = '2.0';
+$VERSION = '2.1';
 
 # Looks nicer without the slashes and dots
 ($ProgramName = $0) =~ s!.*/!!; # strip dir
@@ -54,12 +55,16 @@ $UsageLong = "
            -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:
@@ -69,7 +74,6 @@ $UsageLong = "
 
            -i | --insert-after   x  Insert after this/matching line.
            -f | --insert-before  x  Insert before this/matching line.
-           -R | --regexp-match      Use regexp mode for line matching.
 
   - Manipulating block marks:
 
@@ -105,7 +109,7 @@ $UsageShort .= " or type  $ProgramName --help  to be choked with help.\n";
 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;
@@ -116,10 +120,12 @@ use constant INSERT_ON_TOP => 3;
 # InsertRemove modes
 use constant DO_REMOVE => 0;
 use constant DO_INSERT => 1;
+use constant DO_CHANGE => 2;
 
 # Operation defaults
 $InsertRemove = DO_INSERT;
 $Placement    = APPEND_AT_END;
+$RegexpMatch  = 1;
 $Comment      = '#';
 $CommentEnd   = '';
 $MatchLine    = '';
@@ -139,6 +145,9 @@ while (@ARGV) {
   elsif (/^-r$/ || /^--remove$/) {
     $InsertRemove = DO_REMOVE;
   }
+  elsif (/^-x$/ || /^--change$/) {
+    $InsertRemove = DO_CHANGE;
+  }
   elsif (/^-m$/ || /^--allow-multi(?:ple)?$/) {
     $Multi = 1;
   }
@@ -166,7 +175,7 @@ while (@ARGV) {
     $Placement = INSERT_ON_TOP;
   }
   elsif (/^-R$/ || /^--regexp(?:-match|-mode)?$/) {
-    $RegexpMatch = 1;
+    $RegexpMatch = 1; # it's the default
   }
   elsif (/^-h$/ || /^--help$/) {
     die $UsageLong;
@@ -190,6 +199,10 @@ while (@ARGV) {
     unshift(@ARGV, $_);
     last;
   }
+  
+  if ($Multi and DO_CHANGE == $InsertRemove) {
+    die "$ProgramName: Cannot use both `--change' and `--allow-multiple'\n";
+  }
 }
 
 $Package = shift  or  die $UsageShort;
@@ -238,8 +251,12 @@ do_it() and exit(0);
 # --------------
 
 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();
@@ -261,10 +278,11 @@ sub slurp() {
 
   # 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() {
@@ -299,18 +317,22 @@ sub add() {
       }
       ++ $lineNo;
     }
-    if (! $added) {
-      warn "$ProgramName: Inserting lines at the end implicitly!\n";
+    if ($MatchLine and ! $added) {
+      warn "$ProgramName: Inserting lines at the end implicitly! No '$MatchLine'\n";
       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 ($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;
@@ -318,12 +340,72 @@ 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);
+  }
+  if ($bm_found and $em_found) {
+    DEBUG and print STDERR "Deleted ". (@Lines - @filtered) ." out of ".scalar(@Lines)." lines\n";
+    @Lines = @filtered;
   }
-  DEBUG and print STDERR "Deleted ". (@Lines - @filtered) ." out of ".scalar(@Lines)." lines\n";
-  @Lines = @filtered;
-  return scalar(@Lines); # whatever
+  elsif ($bm_found and ! $em_found) {
+    # safety exit    
+    die "$ProgramName: no end-mark after begin-mark!\n";
+  }
+  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
@@ -333,7 +415,7 @@ sub actualize() {
   # 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";
   }
@@ -356,10 +438,13 @@ sub actualize() {
       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
@@ -374,12 +459,15 @@ sub actualize() {
     $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)
@@ -404,6 +492,7 @@ sub actualize() {
     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