MayaChemTools

   1 #!/usr/bin/perl -w
   2 #
   3 # File: MergeTextFiles.pl
   4 # Author: Manish Sud <msud@san.rr.com>
   5 #
   6 # Copyright (C) 2024 Manish Sud. All rights reserved.
   7 #
   8 # This file is part of MayaChemTools.
   9 #
  10 # MayaChemTools is free software; you can redistribute it and/or modify it under
  11 # the terms of the GNU Lesser General Public License as published by the Free
  12 # Software Foundation; either version 3 of the License, or (at your option) any
  13 # later version.
  14 #
  15 # MayaChemTools is distributed in the hope that it will be useful, but without
  16 # any warranty; without even the implied warranty of merchantability of fitness
  17 # for a particular purpose.  See the GNU Lesser General Public License for more
  18 # details.
  19 #
  20 # You should have received a copy of the GNU Lesser General Public License
  21 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
  22 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
  23 # Boston, MA, 02111-1307, USA.
  24 #
  25 
  26 use strict;
  27 use FindBin; use lib "$FindBin::Bin/../lib";
  28 use Getopt::Long;
  29 use File::Basename;
  30 use Text::ParseWords;
  31 use Benchmark;
  32 use FileHandle;
  33 use FileUtil;
  34 use TextUtil;
  35 
  36 my($ScriptName, %Options, $StartTime, $EndTime, $TotalTime);
  37 
  38 # Autoflush STDOUT
  39 $| = 1;
  40 
  41 # Starting message...
  42 $ScriptName = basename $0;
  43 print "\n$ScriptName:Starting...\n\n";
  44 $StartTime = new Benchmark;
  45 
  46 # Get the options and setup script...
  47 SetupScriptUsage();
  48 if ($Options{help} || @ARGV < 1) {
  49   die GetUsageFromPod("$FindBin::Bin/$ScriptName");
  50 }
  51 
  52 my(@TextFilesList);
  53 @TextFilesList = ExpandFileNames(\@ARGV, "csv tsv");
  54 
  55 if (@TextFilesList == 1) {
  56   die "Error: Specify more than one text file.\n";
  57 }
  58 
  59 # Process options...
  60 print "Processing options...\n";
  61 my(%OptionsInfo);
  62 ProcessOptions();
  63 
  64 # Setup information about input files...
  65 my(%TextFilesInfo);
  66 print "Checking input text files...\n";
  67 RetrieveTextFilesInfo();
  68 RetrieveColumnsAndKeysInfo();
  69 
  70 # Merge files...
  71 print "\nGenerating new text file $OptionsInfo{NewTextFile}...\n";
  72 MergeTextFiles();
  73 
  74 print "\n$ScriptName:Done...\n\n";
  75 
  76 $EndTime = new Benchmark;
  77 $TotalTime = timediff ($EndTime, $StartTime);
  78 print "Total time: ", timestr($TotalTime), "\n";
  79 
  80 ###############################################################################
  81 
  82 # Merge all valid Text files...
  83 sub MergeTextFiles {
  84   my($Index);
  85 
  86   open NEWTEXTFILE, ">$OptionsInfo{NewTextFile}" or die "Error: Couldn't open $OptionsInfo{NewTextFile}: $! \n";
  87 
  88   WriteNewTextFileColumnLabels(\*NEWTEXTFILE);
  89 
  90   #Open up all the files and skip coumn label line...
  91   @{$TextFilesInfo{FileHandle}} = ();
  92   for $Index (0 .. $#TextFilesList) {
  93     $TextFilesInfo{FileHandle}[$Index] = new FileHandle;
  94 
  95     open $TextFilesInfo{FileHandle}[$Index], "$TextFilesList[$Index]" or die "Error: Couldn't open $TextFilesList[$Index]: $! \n";
  96     GetTextLine($TextFilesInfo{FileHandle}[$Index]);
  97   }
  98 
  99   # Merge files...
 100   if ($OptionsInfo{Keys}) {
 101     MergeColumnValuesUsingKeys(\*NEWTEXTFILE);
 102   }
 103   else {
 104     MergeColumnValues(\*NEWTEXTFILE);
 105   }
 106 
 107   # Close all opened files...
 108   close NEWTEXTFILE;
 109   for $Index (0 .. $#TextFilesList) {
 110     close $TextFilesInfo{FileHandle}[$Index];
 111   }
 112 
 113 }
 114 
 115 # Merge all the column values...
 116 sub MergeColumnValues {
 117   my($NewTextFileRef) = @_;
 118   my($Index, $Line, $InDelim, $Value, $ColNum, @LineWords, @File1LineWords, @ColValues);
 119 
 120   while ($Line = GetTextLine($TextFilesInfo{FileHandle}[0])) {
 121     $InDelim = $TextFilesInfo{InDelim}[0];
 122     @ColValues = ();
 123 
 124     #Collect column values from first file before the merge point...
 125     @File1LineWords = quotewords($InDelim, 0, $Line);
 126     for $ColNum (@{$TextFilesInfo{File1Part1ColNums}}) {
 127       $Value = ($ColNum < @File1LineWords) ? $File1LineWords[$ColNum] : "";
 128       push @ColValues, $Value;
 129     }
 130 
 131     #Collect column values from other text files...
 132     for $Index (1 .. $#TextFilesList) {
 133       $InDelim = $TextFilesInfo{InDelim}[$Index];
 134       if ($Line = GetTextLine($TextFilesInfo{FileHandle}[$Index])) {
 135         @LineWords = quotewords($InDelim, 0, $Line);
 136         for $ColNum (@{$TextFilesInfo{ColToMerge}[$Index]}) {
 137           $Value = ($ColNum < @LineWords) ? $LineWords[$ColNum] : "";
 138           push @ColValues, $Value;
 139         }
 140       }
 141     }
 142 
 143     #Collect column labels from first file after the merge point...
 144     for $ColNum (@{$TextFilesInfo{File1Part2ColNums}}) {
 145       $Value = ($ColNum < @File1LineWords) ? $File1LineWords[$ColNum] : "";
 146       push @ColValues, $Value;
 147     }
 148 
 149     # Write it out...
 150     $Line = JoinWords(\@ColValues, $OptionsInfo{OutDelim}, $OptionsInfo{OutQuote});
 151     print $NewTextFileRef "$Line\n";
 152   }
 153 
 154 }
 155 
 156 # Merge column values using keys...
 157 sub MergeColumnValuesUsingKeys {
 158   my($NewTextFileRef) = @_;
 159   my($Index, $InDelim, $Line, $Value, $ColNum, $KeyColNum, $KeyColValue, @LineWords, @ColValues, @File1LineWords, @TextFilesKeysToLinesMap);
 160 
 161   @TextFilesKeysToLinesMap = ();
 162 
 163   # Retrieve text lines from all the files except for the first file...
 164   for $Index (1 .. $#TextFilesList) {
 165     %{$TextFilesKeysToLinesMap[$Index]} = ();
 166 
 167     $InDelim = $TextFilesInfo{InDelim}[$Index];
 168     $KeyColNum = $TextFilesInfo{KeysToUse}[$Index];
 169 
 170     while ($Line = GetTextLine($TextFilesInfo{FileHandle}[$Index])) {
 171       @LineWords = quotewords($InDelim, 0, $Line);
 172       if ($KeyColNum < @LineWords) {
 173         $KeyColValue = $LineWords[$KeyColNum];
 174         if (length($KeyColValue)) {
 175           if (exists($TextFilesKeysToLinesMap[$Index]{$KeyColValue})) {
 176             warn "Warning: Ignoring line, $Line, in text file $TextFilesList[$Index]: Column key value, $KeyColValue, already exists\n";
 177           }
 178           else {
 179             @{$TextFilesKeysToLinesMap[$Index]{$KeyColValue}} = ();
 180             push @{$TextFilesKeysToLinesMap[$Index]{$KeyColValue}}, @LineWords;
 181           }
 182         }
 183       }
 184     }
 185   }
 186 
 187   while ($Line = GetTextLine($TextFilesInfo{FileHandle}[0])) {
 188     $InDelim = $TextFilesInfo{InDelim}[0];
 189 
 190     @ColValues = ();
 191     @File1LineWords = quotewords($InDelim, 0, $Line);
 192 
 193     $KeyColNum = $TextFilesInfo{KeysToUse}[0];
 194     $KeyColValue = $File1LineWords[$KeyColNum];
 195 
 196     #Collect column values from first file before the merge point...
 197     for $ColNum (@{$TextFilesInfo{File1Part1ColNums}}) {
 198       $Value = ($ColNum < @File1LineWords) ? $File1LineWords[$ColNum] : "";
 199       push @ColValues, $Value;
 200     }
 201 
 202     #Collect column values from other text files...
 203     for $Index (1 .. $#TextFilesList) {
 204       @LineWords = ();
 205       if (exists($TextFilesKeysToLinesMap[$Index]{$KeyColValue})) {
 206         push @LineWords, @{$TextFilesKeysToLinesMap[$Index]{$KeyColValue}};
 207       }
 208       for $ColNum (@{$TextFilesInfo{ColToMerge}[$Index]}) {
 209         $Value = ($ColNum < @LineWords) ? $LineWords[$ColNum] : "";
 210         push @ColValues, $Value;
 211       }
 212     }
 213 
 214     #Collect column labels from first file after the merge point...
 215     for $ColNum (@{$TextFilesInfo{File1Part2ColNums}}) {
 216       $Value = ($ColNum < @File1LineWords) ? $File1LineWords[$ColNum] : "";
 217       push @ColValues, $Value;
 218     }
 219 
 220     # Write it out...
 221     $Line = JoinWords(\@ColValues, $OptionsInfo{OutDelim}, $OptionsInfo{OutQuote});
 222     print $NewTextFileRef "$Line\n";
 223   }
 224 
 225 }
 226 
 227 # Write out column labels for new merged text file...
 228 sub WriteNewTextFileColumnLabels {
 229   my($NewTextFileRef) = @_;
 230   my($Index, $Line, $ColNum, @ColLabels);
 231 
 232   #Write out column labels for the merged text file...
 233   @ColLabels = ();
 234 
 235   #Collect column labels from first file before the merge point...
 236   for $ColNum (@{$TextFilesInfo{File1Part1ColNums}}) {
 237     push @ColLabels, $TextFilesInfo{ColToMergeNumToLabelMap}[0]{$ColNum};
 238   }
 239 
 240   #Collect column labels from other text files...
 241   for $Index (1 .. $#TextFilesList) {
 242     for $ColNum (@{$TextFilesInfo{ColToMerge}[$Index]}) {
 243       push @ColLabels, $TextFilesInfo{ColToMergeNumToLabelMap}[$Index]{$ColNum};
 244     }
 245   }
 246 
 247   #Collect column labels from first file after the merge point...
 248   for $ColNum (@{$TextFilesInfo{File1Part2ColNums}}) {
 249     push @ColLabels, $TextFilesInfo{ColToMergeNumToLabelMap}[0]{$ColNum};
 250   }
 251 
 252   #Write it out...
 253   $Line = JoinWords(\@ColLabels, $OptionsInfo{OutDelim}, $OptionsInfo{OutQuote});
 254   print NEWTEXTFILE "$Line\n";
 255 }
 256 
 257 # Retrieve text file columns and keys information for specified options...
 258 sub RetrieveColumnsAndKeysInfo {
 259   ProcessColumnsInfo();
 260 
 261   if ($OptionsInfo{Keys}) {
 262     ProcessKeysInfo();
 263   }
 264 
 265   ProcessStartColInfo();
 266 }
 267 
 268 # Process specified columns...
 269 sub ProcessColumnsInfo {
 270   my($Index, $SpecifiedColNum, $Values, $ColIndex, $ColNum, $ColLabel, @Words);
 271 
 272   @{$TextFilesInfo{ColSpecified}} = ();
 273   @{$TextFilesInfo{ColToMerge}} = ();
 274   @{$TextFilesInfo{ColToMergeNumToLabelMap}} = ();
 275 
 276   for $Index (0 .. $#TextFilesList) {
 277 
 278     @{$TextFilesInfo{ColSpecified}[$Index]} = ();
 279 
 280     $Values = "all";
 281     if ($OptionsInfo{Columns}) {
 282       $Values = $OptionsInfo{ColValues}[$Index];
 283     }
 284 
 285     if ($Values =~ /all/i) {
 286       if ($OptionsInfo{Mode} =~ /^colnum$/i) {
 287         for $ColNum (1 .. $TextFilesInfo{ColCount}[$Index]) {
 288           push @{$TextFilesInfo{ColSpecified}[$Index]}, $ColNum;
 289         }
 290       }
 291       else {
 292         push @{$TextFilesInfo{ColSpecified}[$Index]}, @{$TextFilesInfo{ColLabels}[$Index]};
 293       }
 294     }
 295     else {
 296       @Words = split ",", $Values;
 297       push @{$TextFilesInfo{ColSpecified}[$Index]}, @Words;
 298     }
 299 
 300     @{$TextFilesInfo{ColToMerge}[$Index]} = ();
 301     %{$TextFilesInfo{ColToMergeNumToLabelMap}[$Index]} = ();
 302 
 303     if ($OptionsInfo{Mode} =~ /^collabel$/i) {
 304       for $ColIndex (0 .. $#{$TextFilesInfo{ColSpecified}[$Index]}) {
 305         $ColLabel = $TextFilesInfo{ColSpecified}[$Index][$ColIndex];
 306         if (exists($TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel})) {
 307           $ColNum = $TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel};
 308           push @{$TextFilesInfo{ColToMerge}[$Index]}, $ColNum;
 309           $TextFilesInfo{ColToMergeNumToLabelMap}[$Index]{$ColNum} = $ColLabel;
 310         }
 311         else {
 312           warn "Warning: Ignoring value, $ColLabel, specified using \"-c --column\" option: column name doesn't exist in  $TextFilesList[$Index]  \n";
 313         }
 314       }
 315     }
 316     else {
 317       for $ColIndex (0 .. $#{$TextFilesInfo{ColSpecified}[$Index]}) {
 318         $SpecifiedColNum = $TextFilesInfo{ColSpecified}[$Index][$ColIndex];
 319         if ($SpecifiedColNum > 0 && $SpecifiedColNum <= $TextFilesInfo{ColCount}[$Index]) {
 320           $ColNum = $SpecifiedColNum - 1;
 321           push @{$TextFilesInfo{ColToMerge}[$Index]}, $ColNum;
 322           $TextFilesInfo{ColToMergeNumToLabelMap}[$Index]{$ColNum} = $TextFilesInfo{ColLabels}[$Index][$ColNum];
 323         }
 324         else {
 325           warn "Warning: Ignoring value, $SpecifiedColNum, specified using \"-c --column\" option: column number doesn't exist in  $TextFilesList[$Index]  \n";
 326         }
 327       }
 328     }
 329     my (@ColToMergeSorted) = sort { $a <=> $b } @{$TextFilesInfo{ColToMerge}[$Index]};
 330     @{$TextFilesInfo{ColToMerge}[$Index]} = ();
 331     push @{$TextFilesInfo{ColToMerge}[$Index]}, @ColToMergeSorted;
 332   }
 333 }
 334 
 335 # Process specified key column values...
 336 sub ProcessKeysInfo {
 337   my($Index, $Key, $ColLabel, $ColNum);
 338 
 339   @{$TextFilesInfo{KeysSpecified}} = ();
 340   @{$TextFilesInfo{KeysToUse}} = ();
 341 
 342   for $Index (0 .. $#TextFilesList) {
 343     $Key = $OptionsInfo{KeyValues}[$Index];
 344 
 345     $TextFilesInfo{KeysSpecified}[$Index] = $Key;
 346     $TextFilesInfo{KeysToUse}[$Index] = -1;
 347 
 348     if ($OptionsInfo{Mode} =~ /^collabel$/i) {
 349       $ColLabel = $Key;
 350       if (exists($TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel})) {
 351         $TextFilesInfo{KeysToUse}[$Index] =  $TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel};
 352       }
 353       else {
 354         warn "Warning: Ignoring value, $ColLabel, specified using \"-k --keys\" option: column name doesn't exist in  $TextFilesList[$Index]  \n";
 355       }
 356     }
 357     else {
 358       $ColNum = $Key;
 359       if ($ColNum > 0 && $ColNum <= $TextFilesInfo{ColCount}[$Index]) {
 360         $TextFilesInfo{KeysToUse}[$Index] = $ColNum - 1;
 361       }
 362       else {
 363         warn "Warning: Ignoring value, $ColNum, specified using \"-k --keys\" option: column number doesn't exist in  $TextFilesList[$Index]  \n";
 364       }
 365     }
 366   }
 367 
 368   # Modify columns to merge list to make sure the columns identified by key are taken off the list
 369   # except for the first text file...
 370   my(@ColToMergeFiltered);
 371 
 372   for $Index (1 .. $#TextFilesList) {
 373     @ColToMergeFiltered = ();
 374     for $ColNum (@{$TextFilesInfo{ColToMerge}[$Index]}) {
 375       if ($TextFilesInfo{KeysToUse}[$Index] != $ColNum) {
 376         push @ColToMergeFiltered, $ColNum;
 377       }
 378     }
 379     @{$TextFilesInfo{ColToMerge}[$Index]} = ();
 380     push @{$TextFilesInfo{ColToMerge}[$Index]}, @ColToMergeFiltered;
 381   }
 382 }
 383 
 384 # Process specified start column value...
 385 sub ProcessStartColInfo {
 386   my($Index, $ColIndex, $ColNum, $StartColNum, $Part1StartColNum, $Part1EndColNum, $Part2StartColNum, $Part2EndColNum, $BeforeStartColNum, $AfterStartColNum, $FirstColNum, $LastColNum, $FirstIndex, $LastIndex);
 387 
 388   @{$TextFilesInfo{File1Part1ColNums}} = ();
 389   @{$TextFilesInfo{File1Part2ColNums}} = ();
 390 
 391   $StartColNum = "last";
 392   if ($OptionsInfo{StartCol}) {
 393     if (length($OptionsInfo{StartCol})) {
 394       $StartColNum = $OptionsInfo{StartCol}
 395     }
 396   }
 397 
 398   if ($StartColNum !~ /^last$/i) {
 399     if ($OptionsInfo{Mode} =~ /^collabel$/i) {
 400       if (exists($TextFilesInfo{ColLabelToNumMap}[0]{$StartColNum})) {
 401         $StartColNum = $TextFilesInfo{ColLabelToNumMap}[0]{$StartColNum};
 402       }
 403       else {
 404         die "Error: Invalid value $StartColNum specified using \"-s --startcol\" option: column name doesn't exist in  $TextFilesList[0]  \n";
 405       }
 406     }
 407     else {
 408       if ($StartColNum > 0 && $StartColNum <= $TextFilesInfo{ColCount}[0]) {
 409         $StartColNum -= 1;
 410       }
 411       else {
 412         die "Error: Invalid value $StartColNum specified using \"-s --startcol\" option: column number doesn't exist in  $TextFilesList[0]  \n";
 413       }
 414     }
 415   }
 416   else {
 417     $StartColNum = $TextFilesInfo{ColCount}[0] - 1;
 418   }
 419 
 420   # Make sure StartColNum is present on the list of columns to merge for the first text file...
 421   if (!exists($TextFilesInfo{ColToMergeNumToLabelMap}[0]{$StartColNum})) {
 422     die "Error: Invalid value $StartColNum specified using \"-s --startcol\" option: doesn't exist in the specified lists of columns to merge for  $TextFilesList[0]  \n";
 423   }
 424 
 425   # Find out the column number before and after StartColNum in first text file...
 426   $BeforeStartColNum = $StartColNum;
 427   $AfterStartColNum = $StartColNum;
 428 
 429   $FirstIndex = 0; $LastIndex = $#{$TextFilesInfo{ColToMerge}[0]};
 430 
 431   $FirstColNum = $TextFilesInfo{ColToMerge}[0][$FirstIndex];
 432   $LastColNum = $TextFilesInfo{ColToMerge}[0][$LastIndex];
 433 
 434   for $Index (0 .. $LastIndex) {
 435     if ($TextFilesInfo{ColToMerge}[0][$Index] == $StartColNum) {
 436       $BeforeStartColNum = (($Index -1) >= $FirstIndex) ? $TextFilesInfo{ColToMerge}[0][$Index - 1] : ($FirstColNum - 1);
 437       $AfterStartColNum = (($Index + 1) <= $LastIndex) ? $TextFilesInfo{ColToMerge}[0][$Index + 1] : ($LastColNum + 1);
 438     }
 439   }
 440 
 441   if ($OptionsInfo{StartColMode} =~ /^after$/i) {
 442     $Part1StartColNum = $FirstColNum; $Part1EndColNum = $StartColNum;
 443     $Part2StartColNum = $AfterStartColNum; $Part2EndColNum = $LastColNum;
 444   }
 445   else {
 446     $Part1StartColNum = $FirstColNum; $Part1EndColNum = $BeforeStartColNum;
 447     $Part2StartColNum = $StartColNum; $Part2EndColNum = $LastColNum;
 448   }
 449 
 450   @{$TextFilesInfo{File1Part1ColNums}} = ();
 451   @{$TextFilesInfo{File1Part2ColNums}} = ();
 452 
 453   for $ColIndex (0 .. $#{$TextFilesInfo{ColToMerge}[0]}) {
 454     $ColNum = $TextFilesInfo{ColToMerge}[0][$ColIndex];
 455     if ($ColNum >= $Part1StartColNum && $ColNum <= $Part1EndColNum) {
 456       push @{$TextFilesInfo{File1Part1ColNums}}, $ColNum;
 457     }
 458   }
 459 
 460   for $ColIndex (0 .. $#{$TextFilesInfo{ColToMerge}[0]}) {
 461     $ColNum = $TextFilesInfo{ColToMerge}[0][$ColIndex];
 462     if ($ColNum >= $Part2StartColNum && $ColNum <= $Part2EndColNum) {
 463       push @{$TextFilesInfo{File1Part2ColNums}}, $ColNum;
 464     }
 465   }
 466 
 467 }
 468 
 469 # Retrieve information about input text files...
 470 sub RetrieveTextFilesInfo {
 471   my($Index, $TextFile, $FileDir, $FileName, $FileExt, $InDelim, $Line, $ColNum, $ColLabel, $FileNotOkayCount, @ColLabels,);
 472 
 473   %TextFilesInfo = ();
 474 
 475   @{$TextFilesInfo{FileOkay}} = ();
 476   @{$TextFilesInfo{ColCount}} = ();
 477   @{$TextFilesInfo{ColLabels}} = ();
 478   @{$TextFilesInfo{ColLabelToNumMap}} = ();
 479   @{$TextFilesInfo{InDelim}} = ();
 480 
 481   $FileNotOkayCount = 0;
 482 
 483   FILELIST: for $Index (0 .. $#TextFilesList) {
 484     $TextFile = $TextFilesList[$Index];
 485 
 486     $TextFilesInfo{FileOkay}[$Index] = 0;
 487     $TextFilesInfo{ColCount}[$Index] = 0;
 488     $TextFilesInfo{InDelim}[$Index] = "";
 489 
 490     @{$TextFilesInfo{ColLabels}[$Index]} = ();
 491     %{$TextFilesInfo{ColLabelToNumMap}[$Index]} = ();
 492 
 493     if (!(-e $TextFile)) {
 494       warn "Warning: Ignoring file $TextFile: It doesn't exist\n";
 495       $FileNotOkayCount++;
 496       next FILELIST;
 497     }
 498     if (!CheckFileType($TextFile, "csv tsv")) {
 499       warn "Warning: Ignoring file $TextFile: It's not a csv or tsv file\n";
 500       $FileNotOkayCount++;
 501       next FILELIST;
 502     }
 503     ($FileDir, $FileName, $FileExt) = ParseFileName($TextFile);
 504     if ($FileExt =~ /^tsv$/i) {
 505       $InDelim = "\t";
 506     }
 507     else {
 508       $InDelim = "\,";
 509       if ($OptionsInfo{InDelim} !~ /^(comma|semicolon)$/i) {
 510         warn "Warning: Ignoring file $TextFile: The value specified, $OptionsInfo{InDelim}, for option \"--indelim\" is not valid for csv files\n";
 511         $FileNotOkayCount++;
 512         next FILELIST;
 513       }
 514       if ($OptionsInfo{InDelim} =~ /^semicolon$/i) {
 515         $InDelim = "\;";
 516       }
 517     }
 518 
 519     if (!open TEXTFILE, "$TextFile") {
 520       warn "Warning: Ignoring file $TextFile: Couldn't open it: $! \n";
 521       $FileNotOkayCount++;
 522       next FILELIST;
 523     }
 524 
 525     $Line = GetTextLine(\*TEXTFILE);
 526     @ColLabels = quotewords($InDelim, 0, $Line);
 527     close TEXTFILE;
 528 
 529     $TextFilesInfo{FileOkay}[$Index] = 1;
 530     $TextFilesInfo{InDelim}[$Index] = $InDelim;
 531 
 532     $TextFilesInfo{ColCount}[$Index] = @ColLabels;
 533     push @{$TextFilesInfo{ColLabels}[$Index]}, @ColLabels;
 534     for $ColNum (0 .. $#ColLabels) {
 535       $ColLabel = $ColLabels[$ColNum];
 536       $TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel} = $ColNum;
 537     }
 538   }
 539   # Make sure all specified files are valid for merging to work properly...
 540   if ($FileNotOkayCount) {
 541     die "Error: Problems with input text file(s)...\n";
 542   }
 543 }
 544 
 545 # Process option values...
 546 sub ProcessOptions {
 547   my($Index, $FileDir, $FileName, $FileExt, $NewTextFile, @ColValues, @KeyValues);
 548 
 549   %OptionsInfo = ();
 550 
 551   $OptionsInfo{Mode} = $Options{mode};
 552 
 553   $OptionsInfo{Columns} = $Options{columns};
 554   @{$OptionsInfo{ColValues}} = ();
 555 
 556   if ($Options{columns}) {
 557     @ColValues = split ";", $Options{columns};
 558     if (@ColValues != @TextFilesList) {
 559       die "Error: Invalid number of values specified by \"-c --columns\" option: it must be equal to number of input text files.\n";
 560     }
 561     for $Index (0 .. $#ColValues) {
 562       if (!length($ColValues[$Index])) {
 563         die "Error: Invalid value specified by \"-c --columns\" option: empty values are not allowed.\n";
 564       }
 565     }
 566     @{$OptionsInfo{ColValues}} = @ColValues;
 567   }
 568 
 569   $OptionsInfo{Keys} = $Options{keys};
 570   @{$OptionsInfo{KeyValues}} = ();
 571 
 572   if ($Options{keys}) {
 573     @KeyValues = split ";", $Options{keys};
 574     if (@KeyValues != @TextFilesList) {
 575       die "Error: Invalid number of values specified by \"-k --keys\" option: it must be equal to number of input text files.\n";
 576     }
 577     for $Index (0 .. $#KeyValues) {
 578       if (!length($KeyValues[$Index])) {
 579         die "Error: Invalid value specified by \"-k --keys\" option: empty values are not allowed.\n";
 580       }
 581     }
 582     @{$OptionsInfo{KeyValues}} = @KeyValues;
 583   }
 584 
 585   $OptionsInfo{InDelim} = $Options{indelim};
 586 
 587   $OptionsInfo{StartCol} = $Options{startcol} ? $Options{startcol} : undef;
 588   $OptionsInfo{StartColMode} = $Options{startcolmode};
 589 
 590   $OptionsInfo{OutFileRoot} = $Options{root} ? $Options{root} : undef;
 591   $OptionsInfo{Overwrite} = $Options{overwrite} ? $Options{overwrite} : undef;
 592 
 593   $OptionsInfo{OutDelim} = ($Options{outdelim} =~ /^tab$/i ) ? "\t" : (($Options{outdelim} =~ /^semicolon$/i) ? "\;" : "\,");
 594   $OptionsInfo{OutQuote} = ($Options{quote} =~ /^yes$/i) ? 1 : 0;
 595 
 596   if ($Options{root}) {
 597     $FileDir = ""; $FileName = ""; $FileExt = "";
 598     ($FileDir, $FileName, $FileExt) = ParseFileName($Options{root});
 599     if ($FileName && $FileExt) {
 600       $NewTextFile = $FileName;
 601     } else {
 602       $NewTextFile =  $Options{root};
 603     }
 604   } else {
 605     $FileDir = ""; $FileName = ""; $FileExt = "";
 606     ($FileDir, $FileName, $FileExt) = ParseFileName($TextFilesList[0]);
 607     $NewTextFile = $FileName . "1To" . @TextFilesList . "Merged";
 608   }
 609   if ($Options{outdelim} =~ /^tab$/i) {
 610     $NewTextFile .= ".tsv";
 611   } else {
 612     $NewTextFile .= ".csv";
 613   }
 614   if (!$Options{overwrite}) {
 615     if (-e $NewTextFile) {
 616       die "Error: The file $NewTextFile already exists.\n";
 617     }
 618   }
 619   if ($Options{root}) {
 620     for $Index (0 .. $#TextFilesList) {
 621       if (lc($NewTextFile) eq lc($TextFilesList[$Index])) {
 622         die "Error: Output filename, $NewTextFile, is similar to a input file name.\nSpecify a different name using \"-r --root\" option or use default name.\n";
 623       }
 624     }
 625   }
 626 
 627   $OptionsInfo{NewTextFile} = $NewTextFile;
 628 }
 629 
 630 # Setup script usage  and retrieve command line arguments specified using various options...
 631 sub SetupScriptUsage {
 632 
 633   # Retrieve all the options...
 634   %Options = ();
 635 
 636   $Options{mode} = "colnum";
 637   $Options{indelim} = "comma";
 638   $Options{outdelim} = "comma";
 639   $Options{quote} = "yes";
 640   $Options{startcolmode} = "after";
 641 
 642   if (!GetOptions(\%Options, "help|h", "indelim=s", "columns|c=s", "keys|k=s", "mode|m=s", "outdelim=s", "overwrite|o", "quote|q=s", "root|r=s", "startcol|s=s", "startcolmode=s", "workingdir|w=s")) {
 643     die "\nTo get a list of valid options and their values, use \"$ScriptName -h\" or\n\"perl -S $ScriptName -h\" command and try again...\n";
 644   }
 645   if ($Options{workingdir}) {
 646     if (! -d $Options{workingdir}) {
 647       die "Error: The value specified, $Options{workingdir}, for option \"-w --workingdir\" is not a directory name.\n";
 648     }
 649     chdir $Options{workingdir} or die "Error: Couldn't chdir $Options{workingdir}: $! \n";
 650   }
 651   if ($Options{mode} !~ /^(colnum|collabel)$/i) {
 652     die "Error: The value specified, $Options{mode}, for option \"-m --mode\" is not valid. Allowed values: colnum, or collabel\n";
 653   }
 654   if ($Options{indelim} !~ /^(comma|semicolon)$/i) {
 655     die "Error: The value specified, $Options{indelim}, for option \"--indelim\" is not valid. Allowed values: comma or semicolon\n";
 656   }
 657   if ($Options{outdelim} !~ /^(comma|semicolon|tab)$/i) {
 658     die "Error: The value specified, $Options{outdelim}, for option \"--outdelim\" is not valid. Allowed values: comma, tab, or semicolon\n";
 659   }
 660   if ($Options{quote} !~ /^(yes|no)$/i) {
 661     die "Error: The value specified, $Options{quote}, for option \"-q --quote\" is not valid. Allowed values: yes or no\n";
 662   }
 663   if ($Options{startcolmode} !~ /^(before|after)$/i) {
 664     die "Error: The value specified, $Options{quote}, for option \"--startcolmode\" is not valid. Allowed values: before or after\n";
 665   }
 666 }
 667