1 #!/usr/bin/perl -w 2 # 3 # File: MergeTextFilesWithSD.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 SDFileUtil; 34 use FileUtil; 35 use TextUtil; 36 37 my($ScriptName, %Options, $StartTime, $EndTime, $TotalTime); 38 39 # Autoflush STDOUT 40 $| = 1; 41 42 # Starting message... 43 $ScriptName = basename $0; 44 print "\n$ScriptName:Starting...\n\n"; 45 $StartTime = new Benchmark; 46 47 # Get the options and setup script... 48 SetupScriptUsage(); 49 if ($Options{help} || @ARGV < 1) { 50 die GetUsageFromPod("$FindBin::Bin/$ScriptName"); 51 } 52 53 my($SDFile, @TextFilesList); 54 @TextFilesList = ExpandFileNames(\@ARGV, "csv tsv"); 55 56 if (@TextFilesList < 2) { 57 die "Error: Specify one or more text files.\n"; 58 } 59 $SDFile = shift @TextFilesList; 60 61 # Process options... 62 print "Processing options...\n"; 63 my(%OptionsInfo); 64 ProcessOptions(); 65 66 # Setup information about input files... 67 print "Checking input SD and text files...\n"; 68 my(%TextFilesInfo); 69 ProcessSDFileInfo(); 70 RetrieveTextFilesInfo(); 71 RetrieveColumnsAndKeysInfo(); 72 73 # Merge files... 74 print "\nGenerating new SD file $OptionsInfo{NewSDFile}...\n"; 75 MergeTextFilesWithSD(); 76 77 print "\n$ScriptName:Done...\n\n"; 78 79 $EndTime = new Benchmark; 80 $TotalTime = timediff ($EndTime, $StartTime); 81 print "Total time: ", timestr($TotalTime), "\n"; 82 83 ############################################################################### 84 85 # Merge all valid Text files with SD file... 86 sub MergeTextFilesWithSD { 87 my($Index, $Line); 88 89 open NEWSDFILE, ">$OptionsInfo{NewSDFile}" or die "Error: Couldn't open $OptionsInfo{NewSDFile}: $! \n"; 90 91 open SDFILE, "$SDFile" or die "Error: Couldn't open $SDFile: $! \n"; 92 93 @{$TextFilesInfo{FileHandle}} = (); 94 for $Index (0 .. $#TextFilesList) { 95 $TextFilesInfo{FileHandle}[$Index] = new FileHandle; 96 97 open $TextFilesInfo{FileHandle}[$Index], "$TextFilesList[$Index]" or die "Error: Couldn't open $TextFilesList[$Index]: $! \n"; 98 GetTextLine($TextFilesInfo{FileHandle}[$Index]); 99 } 100 101 if ($OptionsInfo{Keys}) { 102 MergeTextColumnValuesUsingKeys(\*NEWSDFILE, \*SDFILE); 103 } 104 else { 105 MergeTextColumnValues(\*NEWSDFILE, \*SDFILE); 106 } 107 108 # Close all opened files... 109 close NEWSDFILE; 110 close SDFILE; 111 for $Index (0 .. $#TextFilesList) { 112 close $TextFilesInfo{FileHandle}[$Index]; 113 } 114 } 115 116 # Merge the specified text columns into SD file... 117 sub MergeTextColumnValues { 118 my($NewSDFileRef, $SDFileRef) = @_; 119 my($Index, $Value, $CmpdString, $Line, $InDelim, $ColNum, $ColIndex, @ColLabels, @ColValues, @LineWords); 120 121 while ($CmpdString = ReadCmpdString($SDFileRef)) { 122 $CmpdString =~ s/\$\$\$\$$//g; 123 print $NewSDFileRef "$CmpdString"; 124 125 # Merge coulmn values from other text files... 126 @ColLabels = (); @ColValues = (); 127 for $Index (0 .. $#TextFilesList) { 128 push @ColLabels, @{$TextFilesInfo{ColToMergeLabels}[$Index]}; 129 $InDelim = $TextFilesInfo{InDelim}[$Index]; 130 131 if ($Line = GetTextLine($TextFilesInfo{FileHandle}[$Index])) { 132 @LineWords = quotewords($InDelim, 0, $Line); 133 134 for $ColNum (@{$TextFilesInfo{ColToMerge}[$Index]}) { 135 $Value = ($ColNum < @LineWords) ? $LineWords[$ColNum] : ""; 136 push @ColValues, $Value; 137 } 138 } 139 } 140 141 for $ColIndex (0 .. $#ColLabels) { 142 print $NewSDFileRef "> <$ColLabels[$ColIndex]>\n$ColValues[$ColIndex]\n\n"; 143 } 144 print $NewSDFileRef "\$\$\$\$\n"; 145 } 146 } 147 148 # Merge the specified text columns into SD file using keys... 149 sub MergeTextColumnValuesUsingKeys { 150 my($NewSDFileRef, $SDFileRef) = @_; 151 my($Index, $CmpdString, $Value, $InDelim, $KeyColNum, $KeyColValue, $Line, $ColIndex, $ColNum, @ColLabels, @ColValues, @LineWords, @CmpdLines, @TextFilesKeysToLinesMap, %DataFieldValues); 152 153 # Retrieve text lines from all the text files... 154 @TextFilesKeysToLinesMap = (); 155 156 for $Index (0 .. $#TextFilesList) { 157 $InDelim = $TextFilesInfo{InDelim}[$Index]; 158 %{$TextFilesKeysToLinesMap[$Index]} = (); 159 $KeyColNum = $TextFilesInfo{KeysToUse}[$Index]; 160 161 while ($Line = GetTextLine($TextFilesInfo{FileHandle}[$Index])) { 162 @LineWords = quotewords($InDelim, 0, $Line); 163 164 if ($KeyColNum < @LineWords) { 165 $KeyColValue = $LineWords[$KeyColNum]; 166 167 if (length($KeyColValue)) { 168 if (exists($TextFilesKeysToLinesMap[$Index]{$KeyColValue})) { 169 warn "Warning: Ignoring line, $Line, in text file $TextFilesList[$Index]: Column key value, $KeyColValue, already exists\n"; 170 } 171 else { 172 @{$TextFilesKeysToLinesMap[$Index]{$KeyColValue}} = (); 173 push @{$TextFilesKeysToLinesMap[$Index]{$KeyColValue}}, @LineWords; 174 } 175 } 176 } 177 } 178 } 179 180 while ($CmpdString = ReadCmpdString($SDFileRef)) { 181 @CmpdLines = split "\n", $CmpdString; 182 %DataFieldValues = GetCmpdDataHeaderLabelsAndValues(\@CmpdLines); 183 184 if (exists($DataFieldValues{$OptionsInfo{SDKey}})) { 185 @ColLabels = (); @ColValues = (); 186 $CmpdString =~ s/\$\$\$\$$//g; 187 print $NewSDFileRef "$CmpdString"; 188 189 $KeyColValue = $DataFieldValues{$OptionsInfo{SDKey}}; 190 191 # Merge coulmn values from other text files... 192 for $Index (0 .. $#TextFilesList) { 193 push @ColLabels, @{$TextFilesInfo{ColToMergeLabels}[$Index]}; 194 @LineWords = (); 195 196 if (exists($TextFilesKeysToLinesMap[$Index]{$KeyColValue})) { 197 push @LineWords, @{$TextFilesKeysToLinesMap[$Index]{$KeyColValue}}; 198 } 199 200 for $ColNum (@{$TextFilesInfo{ColToMerge}[$Index]}) { 201 $Value = ($ColNum < @LineWords) ? $LineWords[$ColNum] : ""; 202 push @ColValues, $Value; 203 } 204 } 205 206 for $ColIndex (0 .. $#ColLabels) { 207 $Value = (($ColIndex < @ColValues) && IsNotEmpty($ColValues[$ColIndex]) ) ? $ColValues[$ColIndex] : ""; 208 print $NewSDFileRef "> <$ColLabels[$ColIndex]>\n$Value\n\n"; 209 } 210 print $NewSDFileRef "\$\$\$\$\n"; 211 } 212 } 213 } 214 215 # Retrieve text file columns and keys information for specified options... 216 sub RetrieveColumnsAndKeysInfo { 217 ProcessColumnsInfo(); 218 219 if ($OptionsInfo{Keys}) { 220 ProcessKeysInfo(); 221 } 222 } 223 224 # Process specified columns... 225 sub ProcessColumnsInfo { 226 my($Index, $Values, $ColIndex, $ColNum, $ColLabel, @Words); 227 228 @{$TextFilesInfo{ColSpecified}} = (); 229 @{$TextFilesInfo{ColToMerge}} = (); 230 @{$TextFilesInfo{ColToMergeLabels}} = (); 231 @{$TextFilesInfo{ColToMergeNumToLabelMap}} = (); 232 233 for $Index (0 .. $#TextFilesList) { 234 235 @{$TextFilesInfo{ColSpecified}[$Index]} = (); 236 237 $Values = "all"; 238 if ($OptionsInfo{Columns}) { 239 $Values = $OptionsInfo{ColValues}[$Index]; 240 } 241 242 if ($Values =~ /all/i) { 243 if ($OptionsInfo{Mode} =~ /^colnum$/i) { 244 for $ColNum (1 .. $TextFilesInfo{ColCount}[$Index]) { 245 push @{$TextFilesInfo{ColSpecified}[$Index]}, $ColNum; 246 } 247 } else { 248 push @{$TextFilesInfo{ColSpecified}[$Index]}, @{$TextFilesInfo{ColLabels}[$Index]}; 249 } 250 } 251 else { 252 @Words = split ",", $Values; 253 push @{$TextFilesInfo{ColSpecified}[$Index]}, @Words; 254 } 255 256 @{$TextFilesInfo{ColToMerge}[$Index]} = (); 257 %{$TextFilesInfo{ColToMergeNumToLabelMap}[$Index]} = (); 258 259 if ($OptionsInfo{Mode} =~ /^collabel$/i) { 260 for $ColIndex (0 .. $#{$TextFilesInfo{ColSpecified}[$Index]}) { 261 $ColLabel = $TextFilesInfo{ColSpecified}[$Index][$ColIndex]; 262 263 if (exists($TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel})) { 264 $ColNum = $TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel}; 265 push @{$TextFilesInfo{ColToMerge}[$Index]}, $ColNum; 266 $TextFilesInfo{ColToMergeNumToLabelMap}[$Index]{$ColNum} = $ColLabel; 267 } 268 else { 269 warn "Warning: Ignoring value, $ColLabel, specified using \"-c --column\" option: column name doesn't exist in $TextFilesList[$Index] \n"; 270 } 271 } 272 } 273 else { 274 for $ColIndex (0 .. $#{$TextFilesInfo{ColSpecified}[$Index]}) { 275 $ColNum = $TextFilesInfo{ColSpecified}[$Index][$ColIndex]; 276 277 # Make sure it's a numeric value... 278 if (!IsPositiveInteger($ColNum)) { 279 warn "Warning: Ignoring value, $ColNum, specified using \"-c --column\" option: Allowed integer values: > 0\n"; 280 } 281 else { 282 if ($ColNum > 0 && $ColNum <= $TextFilesInfo{ColCount}[$Index]) { 283 $ColNum -= 1; 284 push @{$TextFilesInfo{ColToMerge}[$Index]}, $ColNum; 285 $TextFilesInfo{ColToMergeNumToLabelMap}[$Index]{$ColNum} = $TextFilesInfo{ColLabels}[$Index][$ColNum]; 286 } 287 else { 288 warn "Warning: Ignoring value, $ColNum, specified using \"-c --column\" option: column number doesn't exist in $TextFilesList[$Index] \n"; 289 } 290 } 291 } 292 } 293 294 my (@TextFilesColToMergeSorted) = sort { $a <=> $b } @{$TextFilesInfo{ColToMerge}[$Index]}; 295 296 @{$TextFilesInfo{ColToMerge}[$Index]} = (); 297 push @{$TextFilesInfo{ColToMerge}[$Index]}, @TextFilesColToMergeSorted; 298 299 # Set up the labels... 300 @{$TextFilesInfo{ColToMergeLabels}[$Index]} = (); 301 for $ColNum (@TextFilesColToMergeSorted) { 302 push @{$TextFilesInfo{ColToMergeLabels}[$Index]}, $TextFilesInfo{ColToMergeNumToLabelMap}[$Index]{$ColNum}; 303 } 304 } 305 } 306 307 # Process specified keys.... 308 sub ProcessKeysInfo { 309 my($Index, $ColNum, $ColLabel, $Key); 310 311 @{$TextFilesInfo{KeysSpecified}} = (); 312 @{$TextFilesInfo{KeysToUse}} = (); 313 314 for $Index (0 .. $#TextFilesList) { 315 $Key = $OptionsInfo{KeyValues}[$Index]; 316 317 $TextFilesInfo{KeysSpecified}[$Index] = $Key; 318 $TextFilesInfo{KeysToUse}[$Index] = -1; 319 320 if ($OptionsInfo{Mode} =~ /^collabel$/i) { 321 $ColLabel = $Key; 322 323 if (exists($TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel})) { 324 $TextFilesInfo{KeysToUse}[$Index] = $TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel}; 325 } 326 else { 327 warn "Warning: Ignoring value, $ColLabel, specified using \"-k --keys\" option: column name doesn't exist in $TextFilesList[$Index] \n"; 328 } 329 } 330 else { 331 $ColNum = $Key; 332 if (!IsPositiveInteger($ColNum)) { 333 warn "Warning: Ignoring value, $ColNum, specified using \"-k --keys\" option: Allowed integer values: > 0 \n"; 334 } 335 else { 336 if ($ColNum > 0 && $ColNum <= $TextFilesInfo{ColCount}[$Index]) { 337 $TextFilesInfo{KeysToUse}[$Index] = $ColNum - 1; 338 } 339 else { 340 warn "Warning: Ignoring value, $ColNum, specified using \"-k --keys\" option: column number doesn't exist in $TextFilesList[$Index] \n"; 341 } 342 } 343 } 344 } 345 346 # Modify columns to merge list to make sure the columns identified by key are taken off the list 347 my(@TextFilesColToMergeFiltered, @TextFilesColToMergeLabelsFiltered); 348 349 for $Index (0 .. $#TextFilesList) { 350 @TextFilesColToMergeFiltered = (); 351 @TextFilesColToMergeLabelsFiltered = (); 352 353 for $ColNum (@{$TextFilesInfo{ColToMerge}[$Index]}) { 354 if ($TextFilesInfo{KeysToUse}[$Index] != $ColNum) { 355 push @TextFilesColToMergeFiltered, $ColNum; 356 push @TextFilesColToMergeLabelsFiltered, $TextFilesInfo{ColToMergeNumToLabelMap}[$Index]{$ColNum}; 357 } 358 } 359 360 @{$TextFilesInfo{ColToMerge}[$Index]} = (); 361 push @{$TextFilesInfo{ColToMerge}[$Index]}, @TextFilesColToMergeFiltered; 362 363 @{$TextFilesInfo{ColToMergeLabels}[$Index]} = (); 364 push @{$TextFilesInfo{ColToMergeLabels}[$Index]}, @TextFilesColToMergeLabelsFiltered; 365 } 366 } 367 368 # Check SD file... 369 sub ProcessSDFileInfo { 370 if (!CheckFileType($SDFile, "sd sdf")) { 371 die "Error: Invalid first file $SDFile: It's not a SD file\n"; 372 } 373 if (!(-e $SDFile)) { 374 die "Error: SDFile $SDFile doesn't exist\n"; 375 } 376 } 377 378 # Retrieve information about input text files... 379 sub RetrieveTextFilesInfo { 380 my($Index, $TextFile, $FileDir, $FileName, $FileExt, $InDelim, $Line, $ColNum, $ColLabel, $FileNotOkayCount, @ColLabels,); 381 382 %TextFilesInfo = (); 383 384 @{$TextFilesInfo{FileOkay}} = (); 385 @{$TextFilesInfo{ColCount}} = (); 386 @{$TextFilesInfo{ColLabels}} = (); 387 @{$TextFilesInfo{ColLabelToNumMap}} = (); 388 @{$TextFilesInfo{InDelim}} = (); 389 390 $FileNotOkayCount = 0; 391 392 FILELIST: for $Index (0 .. $#TextFilesList) { 393 $TextFile = $TextFilesList[$Index]; 394 395 $TextFilesInfo{FileOkay}[$Index] = 0; 396 $TextFilesInfo{ColCount}[$Index] = 0; 397 $TextFilesInfo{InDelim}[$Index] = ""; 398 399 @{$TextFilesInfo{ColLabels}[$Index]} = (); 400 %{$TextFilesInfo{ColLabelToNumMap}[$Index]} = (); 401 402 if (!(-e $TextFile)) { 403 warn "Warning: Ignoring file $TextFile: It doesn't exist\n"; 404 $FileNotOkayCount++; 405 next FILELIST; 406 } 407 if (!CheckFileType($TextFile, "csv tsv")) { 408 warn "Warning: Ignoring file $TextFile: It's not a csv or tsv file\n"; 409 $FileNotOkayCount++; 410 next FILELIST; 411 } 412 ($FileDir, $FileName, $FileExt) = ParseFileName($TextFile); 413 if ($FileExt =~ /^tsv$/i) { 414 $InDelim = "\t"; 415 } 416 else { 417 $InDelim = "\,"; 418 if ($OptionsInfo{InDelim} !~ /^(comma|semicolon)$/i) { 419 warn "Warning: Ignoring file $TextFile: The value specified, $OptionsInfo{InDelim}, for option \"--indelim\" is not valid for csv files\n"; 420 $FileNotOkayCount++; 421 next FILELIST; 422 } 423 if ($OptionsInfo{InDelim} =~ /^semicolon$/i) { 424 $InDelim = "\;"; 425 } 426 } 427 428 if (!open TEXTFILE, "$TextFile") { 429 warn "Warning: Ignoring file $TextFile: Couldn't open it: $! \n"; 430 $FileNotOkayCount++; 431 next FILELIST; 432 } 433 434 $Line = GetTextLine(\*TEXTFILE); 435 @ColLabels = quotewords($InDelim, 0, $Line); 436 close TEXTFILE; 437 438 $TextFilesInfo{FileOkay}[$Index] = 1; 439 $TextFilesInfo{InDelim}[$Index] = $InDelim; 440 441 $TextFilesInfo{ColCount}[$Index] = @ColLabels; 442 push @{$TextFilesInfo{ColLabels}[$Index]}, @ColLabels; 443 for $ColNum (0 .. $#ColLabels) { 444 $ColLabel = $ColLabels[$ColNum]; 445 $TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel} = $ColNum; 446 } 447 } 448 # Make sure all specified files are valid for merging to work properly... 449 if ($FileNotOkayCount) { 450 die "Error: Problems with input text file(s)...\n"; 451 } 452 } 453 454 # Process option values... 455 sub ProcessOptions { 456 my($Index, $FileDir, $FileName, $FileExt, $NewSDFile, @ColValues, @KeyValues); 457 458 %OptionsInfo = (); 459 460 $OptionsInfo{Mode} = $Options{mode}; 461 462 $OptionsInfo{Columns} = $Options{columns}; 463 @{$OptionsInfo{ColValues}} = (); 464 465 if ($Options{columns}) { 466 @ColValues = split ";", $Options{columns}; 467 if (@ColValues != @TextFilesList) { 468 die "Error: Invalid number of values specified by \"-c --columns\" option: it must be equal to number of input text files.\n"; 469 } 470 for $Index (0 .. $#ColValues) { 471 if (!length($ColValues[$Index])) { 472 die "Error: Invalid value specified by \"-c --columns\" option: empty values are not allowed.\n"; 473 } 474 } 475 @{$OptionsInfo{ColValues}} = @ColValues; 476 } 477 478 $OptionsInfo{Keys} = $Options{keys}; 479 @{$OptionsInfo{KeyValues}} = (); 480 481 if ($Options{keys}) { 482 @KeyValues = split ";", $Options{keys}; 483 if (@KeyValues != @TextFilesList) { 484 die "Error: Invalid number of values specified by \"-k --keys\" option: it must be equal to number of input text files.\n"; 485 } 486 for $Index (0 .. $#KeyValues) { 487 if (!length($KeyValues[$Index])) { 488 die "Error: Invalid value specified by \"-k --keys\" option: empty values are not allowed.\n"; 489 } 490 } 491 @{$OptionsInfo{KeyValues}} = @KeyValues; 492 } 493 494 $OptionsInfo{InDelim} = $Options{indelim}; 495 496 $OptionsInfo{OutFileRoot} = defined $Options{root} ? $Options{root} : undef; 497 $OptionsInfo{Overwrite} = defined $Options{overwrite} ? $Options{overwrite} : undef; 498 499 $OptionsInfo{SDKey} = defined $Options{sdkey} ? $Options{sdkey} : undef; 500 501 # Setup new SD file... 502 if ($Options{root}) { 503 $FileDir = ""; $FileName = ""; $FileExt = ""; 504 ($FileDir, $FileName, $FileExt) = ParseFileName($Options{root}); 505 if ($FileName && $FileExt) { 506 $NewSDFile = $FileName; 507 } 508 else { 509 $NewSDFile = $Options{root}; 510 } 511 } 512 else { 513 $FileDir = ""; $FileName = ""; $FileExt = ""; 514 ($FileDir, $FileName, $FileExt) = ParseFileName($SDFile); 515 516 $NewSDFile = $FileName; 517 ($FileDir, $FileName, $FileExt) = ParseFileName($TextFilesList[0]); 518 519 $NewSDFile = $NewSDFile . "MergedWith" . $FileName . "1To" . @TextFilesList; 520 } 521 522 $NewSDFile = $NewSDFile . ".sdf"; 523 if (!$Options{overwrite}) { 524 if (-e $NewSDFile) { 525 die "Error: The file $NewSDFile already exists.\n"; 526 } 527 } 528 if ($Options{root}) { 529 if (lc($NewSDFile) eq lc($SDFile)) { 530 die "Error: Output filename, $NewSDFile, is similar to a input file name.\nSpecify a different name using \"-r --root\" option or use default name.\n"; 531 } 532 } 533 $OptionsInfo{NewSDFile} = $NewSDFile; 534 } 535 536 # Setup script usage and retrieve command line arguments specified using various options... 537 sub SetupScriptUsage { 538 539 # Retrieve all the options... 540 %Options = (); 541 $Options{mode} = "colnum"; 542 $Options{indelim} = "comma"; 543 544 if (!GetOptions(\%Options, "help|h", "indelim=s", "columns|c=s", "keys|k=s", "mode|m=s", "overwrite|o", "root|r=s", "sdkey|s=s", "workingdir|w=s")) { 545 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"; 546 } 547 if ($Options{workingdir}) { 548 if (! -d $Options{workingdir}) { 549 die "Error: The value specified, $Options{workingdir}, for option \"-w --workingdir\" is not a directory name.\n"; 550 } 551 chdir $Options{workingdir} or die "Error: Couldn't chdir $Options{workingdir}: $! \n"; 552 } 553 if ($Options{mode} !~ /^(colnum|collabel)$/i) { 554 die "Error: The value specified, $Options{mode}, for option \"-m --mode\" is not valid. Allowed values: colnum, or collabel\n"; 555 } 556 if ($Options{indelim} !~ /^(comma|semicolon)$/i) { 557 die "Error: The value specified, $Options{indelim}, for option \"--indelim\" is not valid. Allowed values: comma or semicolon\n"; 558 } 559 if ($Options{sdkey} && !$Options{keys}) { 560 die "Error: The option \"-s --sdkey\" can't be specified without the \"-k --keys\" option.\n"; 561 } 562 elsif (!$Options{sdkey} && $Options{keys}) { 563 die "Error: The option \"-k --keys\" can't be specified without the \"-s --sdkey\" option.\n"; 564 } 565 } 566