1 /* 2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 % % 4 % % 5 % % 6 % M M AAA GGGG IIIII CCCC K K % 7 % MM MM A A G I C K K % 8 % M M M AAAAA G GGG I C KKK % 9 % M M A A G G I C K K % 10 % M M A A GGGG IIIII CCCC K K % 11 % % 12 % CCCC L IIIII % 13 % C L I % 14 % C L I % 15 % C L I % 16 % CCCC LLLLL IIIII % 17 % % 18 % Perform "Magick" on Images via the Command Line Interface % 19 % % 20 % Dragon Computing % 21 % Anthony Thyssen % 22 % January 2012 % 23 % % 24 % % 25 % Copyright 1999-2019 ImageMagick Studio LLC, a non-profit organization % 26 % dedicated to making software imaging solutions freely available. % 27 % % 28 % You may not use this file except in compliance with the License. You may % 29 % obtain a copy of the License at % 30 % % 31 % https://imagemagick.org/script/license.php % 32 % % 33 % Unless required by applicable law or agreed to in writing, software % 34 % distributed under the License is distributed on an "AS IS" BASIS, % 35 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % 36 % See the License for the specific language governing permissions and % 37 % limitations under the License. % 38 % % 39 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 40 % 41 % Read CLI arguments, script files, and pipelines, to provide options that 42 % manipulate images from many different formats. 43 % 44 */ 45 46 /* 48 Include declarations. 49 */ 50 #include "MagickWand/studio.h" 51 #include "MagickWand/MagickWand.h" 52 #include "MagickWand/magick-wand-private.h" 53 #include "MagickWand/wandcli.h" 54 #include "MagickWand/wandcli-private.h" 55 #include "MagickWand/operation.h" 56 #include "MagickWand/magick-cli.h" 57 #include "MagickWand/script-token.h" 58 #include "MagickCore/utility-private.h" 59 #include "MagickCore/exception-private.h" 60 #include "MagickCore/version.h" 61 62 /* verbose debugging, 64 0 - no debug lines 65 3 - show option details (better to use -debug Command now) 66 5 - image counts (after option runs) 67 */ 68 #define MagickCommandDebug 0 69 70 71 /* 73 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 74 % % 75 % % 76 % % 77 + P r o c e s s S c r i p t O p t i o n s % 78 % % 79 % % 80 % % 81 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 82 % 83 % ProcessScriptOptions() reads options and processes options as they are 84 % found in the given file, or pipeline. The filename to open and read 85 % options is given as the 'index' argument of the argument array given. 86 % 87 % Other arguments following index may be read by special script options 88 % as settings (strings), images, or as operations to be processed in various 89 % ways. How they are treated is up to the script being processed. 90 % 91 % Note that a script not 'return' to the command line processing, nor can 92 % they call (and return from) other scripts. At least not at this time. 93 % 94 % There are no 'ProcessOptionFlags' control flags at this time. 95 % 96 % The format of the ProcessScriptOptions method is: 97 % 98 % void ProcessScriptOptions(MagickCLI *cli_wand,const char *filename, 99 % int argc,char **argv,int index) 100 % 101 % A description of each parameter follows: 102 % 103 % o cli_wand: the main CLI Wand to use. 104 % 105 % o filename: the filename of script to process 106 % 107 % o argc: the number of elements in the argument vector. (optional) 108 % 109 % o argv: A text array containing the command line arguments. (optional) 110 % 111 % o index: offset of next argment in argv (script arguments) (optional) 112 % 113 */ 114 WandExport void ProcessScriptOptions(MagickCLI *cli_wand,const char *filename, 115 int magick_unused(argc),char **magick_unused(argv),int magick_unused(index)) 116 { 117 ScriptTokenInfo 118 *token_info; 119 120 CommandOptionFlags 121 option_type; 122 123 int 124 count; 125 126 char 127 *option, 128 *arg1, 129 *arg2; 130 131 magick_unreferenced(argc); 132 magick_unreferenced(argv); 133 magick_unreferenced(index); 134 assert(filename != (char *) NULL ); /* at least one argument - script name */ 135 assert(cli_wand != (MagickCLI *) NULL); 136 assert(cli_wand->signature == MagickWandSignature); 137 if (cli_wand->wand.debug != MagickFalse) 138 (void) LogMagickEvent(CommandEvent,GetMagickModule(), 139 "Processing script \"%s\"", filename); 140 141 /* open file script or stream, and set up tokenizer */ 142 token_info = AcquireScriptTokenInfo(filename); 143 if (token_info == (ScriptTokenInfo *) NULL) { 144 CLIWandExceptionFile(OptionFatalError,"UnableToOpenScript",filename); 145 return; 146 } 147 148 /* define the error location string for use in exceptions 149 order of localtion format escapes: filename, line, column */ 150 cli_wand->location="in \"%s\" at line %u,column %u"; 151 if ( LocaleCompare("-", filename) == 0 ) 152 cli_wand->filename="stdin"; 153 else 154 cli_wand->filename=filename; 155 156 /* Process Options from Script */ 157 option = arg1 = arg2 = (char*) NULL; 158 DisableMSCWarning(4127) 159 while (1) { 160 RestoreMSCWarning 161 162 { MagickBooleanType status = GetScriptToken(token_info); 163 cli_wand->line=token_info->token_line; 164 cli_wand->column=token_info->token_column; 165 if (status == MagickFalse) 166 break; /* error or end of options */ 167 } 168 169 do { /* use break to loop to exception handler and loop */ 170 171 /* save option details */ 172 CloneString(&option,token_info->token); 173 174 /* get option, its argument count, and option type */ 175 cli_wand->command = GetCommandOptionInfo(option); 176 count=cli_wand->command->type; 177 option_type=(CommandOptionFlags) cli_wand->command->flags; 178 #if 0 179 (void) FormatLocaleFile(stderr, "Script: %u,%u: \"%s\" matched \"%s\"\n", 180 cli_wand->line, cli_wand->line, option, cli_wand->command->mnemonic ); 181 #endif 182 183 /* handle a undefined option - image read - always for "magick-script" */ 184 if ( option_type == UndefinedOptionFlag || 185 (option_type & NonMagickOptionFlag) != 0 ) { 186 #if MagickCommandDebug >= 3 187 (void) FormatLocaleFile(stderr, "Script %u,%u Non-Option: \"%s\"\n", 188 cli_wand->line, cli_wand->line, option); 189 #endif 190 if (IsCommandOption(option) == MagickFalse) { 191 /* non-option -- treat as a image read */ 192 cli_wand->command=(const OptionInfo *) NULL; 193 CLIOption(cli_wand,"-read",option); 194 break; /* next option */ 195 } 196 CLIWandException(OptionFatalError,"UnrecognizedOption",option); 197 break; /* next option */ 198 } 199 200 if ( count >= 1 ) { 201 if (GetScriptToken(token_info) == MagickFalse) 202 CLIWandException(OptionFatalError,"MissingArgument",option); 203 CloneString(&arg1,token_info->token); 204 } 205 else 206 CloneString(&arg1,(char *) NULL); 207 208 if ( count >= 2 ) { 209 if (GetScriptToken(token_info) == MagickFalse) 210 CLIWandExceptionBreak(OptionFatalError,"MissingArgument",option); 211 CloneString(&arg2,token_info->token); 212 } 213 else 214 CloneString(&arg2,(char *) NULL); 215 216 /* 217 Process Options 218 */ 219 #if MagickCommandDebug >= 3 220 (void) FormatLocaleFile(stderr, 221 "Script %u,%u Option: \"%s\" Count: %d Flags: %04x Args: \"%s\" \"%s\"\n", 222 cli_wand->line,cli_wand->line,option,count,option_type,arg1,arg2); 223 #endif 224 /* Hard Deprecated Options, no code to execute - error */ 225 if ( (option_type & DeprecateOptionFlag) != 0 ) { 226 CLIWandException(OptionError,"DeprecatedOptionNoCode",option); 227 break; /* next option */ 228 } 229 230 /* MagickCommandGenesis() options have no place in a magick script */ 231 if ( (option_type & GenesisOptionFlag) != 0 ) { 232 CLIWandException(OptionError,"InvalidUseOfOption",option); 233 break; /* next option */ 234 } 235 236 /* handle any special 'script' options */ 237 if ( (option_type & SpecialOptionFlag) != 0 ) { 238 if ( LocaleCompare(option,"-exit") == 0 ) { 239 goto loop_exit; /* break out of loop - return from script */ 240 } 241 if ( LocaleCompare(option,"-script") == 0 ) { 242 /* FUTURE: call new script from this script - error for now */ 243 CLIWandException(OptionError,"InvalidUseOfOption",option); 244 break; /* next option */ 245 } 246 /* FUTURE: handle special script-argument options here */ 247 /* handle any other special operators now */ 248 CLIWandException(OptionError,"InvalidUseOfOption",option); 249 break; /* next option */ 250 } 251 252 /* Process non-specific Option */ 253 CLIOption(cli_wand, option, arg1, arg2); 254 (void) fflush(stdout); 255 (void) fflush(stderr); 256 257 DisableMSCWarning(4127) 258 } while (0); /* break block to next option */ 259 RestoreMSCWarning 260 261 #if MagickCommandDebug >= 5 262 fprintf(stderr, "Script Image Count = %ld\n", 263 GetImageListLength(cli_wand->wand.images) ); 264 #endif 265 if (CLICatchException(cli_wand, MagickFalse) != MagickFalse) 266 break; /* exit loop */ 267 } 268 269 /* 270 Loop exit - check for some tokenization error 271 */ 272 loop_exit: 273 #if MagickCommandDebug >= 3 274 (void) FormatLocaleFile(stderr, "Script End: %d\n", token_info->status); 275 #endif 276 switch( token_info->status ) { 277 case TokenStatusOK: 278 case TokenStatusEOF: 279 if (cli_wand->image_list_stack != (Stack *) NULL) 280 CLIWandException(OptionError,"UnbalancedParenthesis", "(eof)"); 281 else if (cli_wand->image_info_stack != (Stack *) NULL) 282 CLIWandException(OptionError,"UnbalancedBraces", "(eof)"); 283 break; 284 case TokenStatusBadQuotes: 285 /* Ensure last token has a sane length for error report */ 286 if( strlen(token_info->token) > INITAL_TOKEN_LENGTH-1 ) { 287 token_info->token[INITAL_TOKEN_LENGTH-4] = '.'; 288 token_info->token[INITAL_TOKEN_LENGTH-3] = '.'; 289 token_info->token[INITAL_TOKEN_LENGTH-2] = '.'; 290 token_info->token[INITAL_TOKEN_LENGTH-1] = '\0'; 291 } 292 CLIWandException(OptionFatalError,"ScriptUnbalancedQuotes", 293 token_info->token); 294 break; 295 case TokenStatusMemoryFailed: 296 CLIWandException(OptionFatalError,"ScriptTokenMemoryFailed",""); 297 break; 298 case TokenStatusBinary: 299 CLIWandException(OptionFatalError,"ScriptIsBinary",""); 300 break; 301 } 302 (void) fflush(stdout); 303 (void) fflush(stderr); 304 if (cli_wand->wand.debug != MagickFalse) 305 (void) LogMagickEvent(CommandEvent,GetMagickModule(), 306 "Script End \"%s\"", filename); 307 308 /* Clean up */ 309 token_info = DestroyScriptTokenInfo(token_info); 310 311 CloneString(&option,(char *) NULL); 312 CloneString(&arg1,(char *) NULL); 313 CloneString(&arg2,(char *) NULL); 314 315 return; 316 } 317 318 /* 320 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 321 % % 322 % % 323 % % 324 + P r o c e s s C o m m a n d O p t i o n s % 325 % % 326 % % 327 % % 328 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 329 % 330 % ProcessCommandOptions() reads and processes arguments in the given 331 % command line argument array. The 'index' defines where in the array we 332 % should begin processing 333 % 334 % The 'process_flags' can be used to control and limit option processing. 335 % For example, to only process one option, or how unknown and special options 336 % are to be handled, and if the last argument in array is to be regarded as a 337 % final image write argument (filename or special coder). 338 % 339 % The format of the ProcessCommandOptions method is: 340 % 341 % int ProcessCommandOptions(MagickCLI *cli_wand,int argc,char **argv, 342 % int index) 343 % 344 % A description of each parameter follows: 345 % 346 % o cli_wand: the main CLI Wand to use. 347 % 348 % o argc: the number of elements in the argument vector. 349 % 350 % o argv: A text array containing the command line arguments. 351 % 352 % o process_flags: What type of arguments will be processed, ignored 353 % or return errors. 354 % 355 % o index: index in the argv array to start processing from 356 % 357 % The function returns the index ot the next option to be processed. This 358 % is really only releven if process_flags contains a ProcessOneOptionOnly 359 % flag. 360 % 361 */ 362 WandExport int ProcessCommandOptions(MagickCLI *cli_wand,int argc,char **argv, 363 int index) 364 { 365 const char 366 *option, 367 *arg1, 368 *arg2; 369 370 int 371 i, 372 end, 373 count; 374 375 CommandOptionFlags 376 option_type; 377 378 assert(argc>=index); /* you may have no arguments left! */ 379 assert(argv != (char **) NULL); 380 assert(argv[index] != (char *) NULL); 381 assert(argv[argc-1] != (char *) NULL); 382 assert(cli_wand != (MagickCLI *) NULL); 383 assert(cli_wand->signature == MagickWandSignature); 384 385 /* define the error location string for use in exceptions 386 order of localtion format escapes: filename, line, column */ 387 cli_wand->location="at %s arg %u"; 388 cli_wand->filename="CLI"; 389 cli_wand->line=index; /* note first argument we will process */ 390 391 if (cli_wand->wand.debug != MagickFalse) 392 (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(), 393 "- Starting (\"%s\")", argv[index]); 394 395 end = argc; 396 if ( (cli_wand->process_flags & ProcessImplictWrite) != 0 ) 397 end--; /* the last arument is an implied write, do not process directly */ 398 399 for (i=index; i < end; i += count +1) { 400 /* Finished processing one option? */ 401 if ( (cli_wand->process_flags & ProcessOneOptionOnly) != 0 && i != index ) 402 return(i); 403 404 do { /* use break to loop to exception handler and loop */ 405 406 option=argv[i]; 407 cli_wand->line=i; /* note the argument for this option */ 408 409 /* get option, its argument count, and option type */ 410 cli_wand->command = GetCommandOptionInfo(argv[i]); 411 count=cli_wand->command->type; 412 option_type=(CommandOptionFlags) cli_wand->command->flags; 413 #if 0 414 (void) FormatLocaleFile(stderr, "CLI %d: \"%s\" matched \"%s\"\n", 415 i, argv[i], cli_wand->command->mnemonic ); 416 #endif 417 418 if ( option_type == UndefinedOptionFlag || 419 (option_type & NonMagickOptionFlag) != 0 ) { 420 #if MagickCommandDebug >= 3 421 (void) FormatLocaleFile(stderr, "CLI arg %d Non-Option: \"%s\"\n", 422 i, option); 423 #endif 424 if (IsCommandOption(option) == MagickFalse) { 425 if ( (cli_wand->process_flags & ProcessImplictRead) != 0 ) { 426 /* non-option -- treat as a image read */ 427 cli_wand->command=(const OptionInfo *) NULL; 428 CLIOption(cli_wand,"-read",option); 429 break; /* next option */ 430 } 431 } 432 CLIWandException(OptionFatalError,"UnrecognizedOption",option); 433 break; /* next option */ 434 } 435 436 if ( ((option_type & SpecialOptionFlag) != 0 ) && 437 ((cli_wand->process_flags & ProcessScriptOption) != 0) && 438 (LocaleCompare(option,"-script") == 0) ) { 439 /* Call Script from CLI, with a filename as a zeroth argument. 440 NOTE: -script may need to use the 'implict write filename' argument 441 so it must be handled specially to prevent a 'missing argument' error. 442 */ 443 if ( (i+count) >= argc ) 444 CLIWandException(OptionFatalError,"MissingArgument",option); 445 ProcessScriptOptions(cli_wand,argv[i+1],argc,argv,i+count); 446 return(argc); /* Script does not return to CLI -- Yet */ 447 /* FUTURE: when it does, their may be no write arg! */ 448 } 449 450 if ((i+count) >= end ) { 451 CLIWandException(OptionFatalError,"MissingArgument",option); 452 if ( CLICatchException(cli_wand, MagickFalse) != MagickFalse ) 453 return(end); 454 break; /* next option - not that their is any! */ 455 } 456 457 arg1 = ( count >= 1 ) ? argv[i+1] : (char *) NULL; 458 arg2 = ( count >= 2 ) ? argv[i+2] : (char *) NULL; 459 460 /* 461 Process Known Options 462 */ 463 #if MagickCommandDebug >= 3 464 (void) FormatLocaleFile(stderr, 465 "CLI arg %u Option: \"%s\" Count: %d Flags: %04x Args: \"%s\" \"%s\"\n", 466 i,option,count,option_type,arg1,arg2); 467 #endif 468 /* ignore 'genesis options' in command line args */ 469 if ( (option_type & GenesisOptionFlag) != 0 ) 470 break; /* next option */ 471 472 /* Handle any special options for CLI (-script handled above) */ 473 if ( (option_type & SpecialOptionFlag) != 0 ) { 474 if ( (cli_wand->process_flags & ProcessExitOption) != 0 475 && LocaleCompare(option,"-exit") == 0 ) 476 return(i+count); 477 break; /* next option */ 478 } 479 480 /* Process standard image option */ 481 CLIOption(cli_wand, option, arg1, arg2); 482 483 DisableMSCWarning(4127) 484 } while (0); /* break block to next option */ 485 RestoreMSCWarning 486 487 #if MagickCommandDebug >= 5 488 (void) FormatLocaleFile(stderr, "CLI-post Image Count = %ld\n", 489 (long) GetImageListLength(cli_wand->wand.images) ); 490 #endif 491 if ( CLICatchException(cli_wand, MagickFalse) != MagickFalse ) 492 return(i+count); 493 } 494 assert(i==end); 495 496 if ( (cli_wand->process_flags & ProcessImplictWrite) == 0 ) 497 return(end); /* no implied write -- just return to caller */ 498 499 assert(end==argc-1); /* end should not include last argument */ 500 501 /* 502 Implicit Write of images to final CLI argument 503 */ 504 option=argv[i]; 505 cli_wand->line=i; 506 507 /* check that stacks are empty - or cause exception */ 508 if (cli_wand->image_list_stack != (Stack *) NULL) 509 CLIWandException(OptionError,"UnbalancedParenthesis", "(end of cli)"); 510 else if (cli_wand->image_info_stack != (Stack *) NULL) 511 CLIWandException(OptionError,"UnbalancedBraces", "(end of cli)"); 512 if ( CLICatchException(cli_wand, MagickFalse) != MagickFalse ) 513 return(argc); 514 515 #if MagickCommandDebug >= 3 516 (void) FormatLocaleFile(stderr,"CLI arg %d Write File: \"%s\"\n",i,option); 517 #endif 518 519 /* Valid 'do no write' replacement option (instead of "null:") */ 520 if (LocaleCompare(option,"-exit") == 0 ) 521 return(argc); /* just exit, no image write */ 522 523 /* If filename looks like an option, 524 Or the common 'end of line' error of a single space. 525 -- produce an error */ 526 if (IsCommandOption(option) != MagickFalse || 527 (option[0] == ' ' && option[1] == '\0') ) { 528 CLIWandException(OptionError,"MissingOutputFilename",option); 529 return(argc); 530 } 531 532 cli_wand->command=(const OptionInfo *) NULL; 533 CLIOption(cli_wand,"-write",option); 534 return(argc); 535 } 536 537 /* 539 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 540 % % 541 % % 542 % % 543 + M a g i c k I m a g e C o m m a n d % 544 % % 545 % % 546 % % 547 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 548 % 549 % MagickImageCommand() Handle special use CLI arguments and prepare a 550 % CLI MagickCLI to process the command line or directly specified script. 551 % 552 % This is essentualy interface function between the MagickCore library 553 % initialization function MagickCommandGenesis(), and the option MagickCLI 554 % processing functions ProcessCommandOptions() or ProcessScriptOptions() 555 % 556 % The format of the MagickImageCommand method is: 557 % 558 % MagickBooleanType MagickImageCommand(ImageInfo *image_info,int argc, 559 % char **argv,char **metadata,ExceptionInfo *exception) 560 % 561 % A description of each parameter follows: 562 % 563 % o image_info: the starting image_info structure 564 % (for compatibilty with MagickCommandGenisis()) 565 % 566 % o argc: the number of elements in the argument vector. 567 % 568 % o argv: A text array containing the command line arguments. 569 % 570 % o metadata: any metadata (for VBS) is returned here. 571 % (for compatibilty with MagickCommandGenisis()) 572 % 573 % o exception: return any errors or warnings in this structure. 574 % 575 */ 576 577 static void MagickUsage(MagickBooleanType verbose) 578 { 579 const char 580 *name; 581 582 size_t 583 len; 584 585 name=GetClientName(); 586 len=strlen(name); 587 588 if (len>=7 && LocaleCompare("convert",name+len-7) == 0) { 589 /* convert usage */ 590 (void) FormatLocaleFile(stdout, 591 "Usage: %s [ {option} | {image} ... ] {output_image}\n",name); 592 (void) FormatLocaleFile(stdout, 593 " %s -help | -version | -usage | -list {option}\n\n",name); 594 return; 595 } 596 else if (len>=6 && LocaleCompare("script",name+len-6) == 0) { 597 /* magick-script usage */ 598 (void) FormatLocaleFile(stdout, 599 "Usage: %s {filename} [ {script_args} ... ]\n",name); 600 } 601 else { 602 /* magick usage */ 603 (void) FormatLocaleFile(stdout, 604 "Usage: %s tool [ {option} | {image} ... ] {output_image}\n",name); 605 (void) FormatLocaleFile(stdout, 606 "Usage: %s [ {option} | {image} ... ] {output_image}\n",name); 607 (void) FormatLocaleFile(stdout, 608 " %s [ {option} | {image} ... ] -script {filename} [ {script_args} ...]\n", 609 name); 610 } 611 (void) FormatLocaleFile(stdout, 612 " %s -help | -version | -usage | -list {option}\n\n",name); 613 614 if (verbose == MagickFalse) 615 return; 616 617 (void) FormatLocaleFile(stdout,"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", 618 "All options are performed in a strict 'as you see them' order\n", 619 "You must read-in images before you can operate on them.\n", 620 "\n", 621 "Magick Script files can use any of the following forms...\n", 622 " #!/path/to/magick -script\n", 623 "or\n", 624 " #!/bin/sh\n", 625 " :; exec magick -script \"$0\" \"$@\"; exit 10\n", 626 " # Magick script from here...\n", 627 "or\n", 628 " #!/usr/bin/env magick-script\n", 629 "The latter two forms do not require the path to the command hard coded.\n", 630 "Note: \"magick-script\" needs to be linked to the \"magick\" command.\n", 631 "\n", 632 "For more information on usage, options, examples, and techniques\n", 633 "see the ImageMagick website at ", MagickAuthoritativeURL); 634 635 return; 636 } 637 638 /* 639 Concatanate given file arguments to the given output argument. 640 Used for a special -concatenate option used for specific 'delegates'. 641 The option is not formally documented. 642 643 magick -concatenate files... output 644 645 This is much like the UNIX "cat" command, but for both UNIX and Windows, 646 however the last argument provides the output filename. 647 */ 648 static MagickBooleanType ConcatenateImages(int argc,char **argv, 649 ExceptionInfo *exception ) 650 { 651 FILE 652 *input, 653 *output; 654 655 MagickBooleanType 656 status; 657 658 int 659 c; 660 661 register ssize_t 662 i; 663 664 if (ExpandFilenames(&argc,&argv) == MagickFalse) 665 ThrowFileException(exception,ResourceLimitError,"MemoryAllocationFailed", 666 GetExceptionMessage(errno)); 667 output=fopen_utf8(argv[argc-1],"wb"); 668 if (output == (FILE *) NULL) 669 { 670 ThrowFileException(exception,FileOpenError,"UnableToOpenFile", 671 argv[argc-1]); 672 return(MagickFalse); 673 } 674 status=MagickTrue; 675 for (i=2; i < (ssize_t) (argc-1); i++) 676 { 677 input=fopen_utf8(argv[i],"rb"); 678 if (input == (FILE *) NULL) 679 { 680 ThrowFileException(exception,FileOpenError,"UnableToOpenFile",argv[i]); 681 continue; 682 } 683 for (c=fgetc(input); c != EOF; c=fgetc(input)) 684 if (fputc((char) c,output) != c) 685 status=MagickFalse; 686 (void) fclose(input); 687 (void) remove_utf8(argv[i]); 688 } 689 (void) fclose(output); 690 return(status); 691 } 692 693 WandExport MagickBooleanType MagickImageCommand(ImageInfo *image_info,int argc, 694 char **argv,char **metadata,ExceptionInfo *exception) 695 { 696 MagickCLI 697 *cli_wand; 698 699 size_t 700 len; 701 702 assert(image_info != (ImageInfo *) NULL); 703 704 /* For specific OS command line requirements */ 705 ReadCommandlLine(argc,&argv); 706 707 /* Initialize special "CLI Wand" to hold images and settings (empty) */ 708 cli_wand=AcquireMagickCLI(image_info,exception); 709 cli_wand->location="Initializing"; 710 cli_wand->filename=argv[0]; 711 cli_wand->line=1; 712 713 if (cli_wand->wand.debug != MagickFalse) 714 (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(), 715 "\"%s\"",argv[0]); 716 717 718 GetPathComponent(argv[0],TailPath,cli_wand->wand.name); 719 SetClientName(cli_wand->wand.name); 720 ConcatenateMagickString(cli_wand->wand.name,"-CLI",MagickPathExtent); 721 722 len=strlen(argv[0]); /* precaution */ 723 724 /* "convert" command - give a "deprecated" warning" */ 725 if (len>=7 && LocaleCompare("convert",argv[0]+len-7) == 0) { 726 cli_wand->process_flags = ConvertCommandOptionFlags; 727 (void) FormatLocaleFile(stderr,"WARNING: %s\n", 728 "The convert command is deprecated in IMv7, use \"magick\"\n"); 729 } 730 731 /* Special Case: If command name ends with "script" implied "-script" */ 732 if (len>=6 && LocaleCompare("script",argv[0]+len-6) == 0) { 733 if (argc >= 2 && ( (*(argv[1]) != '-') || (strlen(argv[1]) == 1) )) { 734 GetPathComponent(argv[1],TailPath,cli_wand->wand.name); 735 ProcessScriptOptions(cli_wand,argv[1],argc,argv,2); 736 goto Magick_Command_Cleanup; 737 } 738 } 739 740 /* Special Case: Version Information and Abort */ 741 if (argc == 2) { 742 if ((LocaleCompare("-version",argv[1]) == 0) || /* GNU standard option */ 743 (LocaleCompare("--version",argv[1]) == 0) ) { /* just version */ 744 CLIOption(cli_wand, "-version"); 745 goto Magick_Command_Exit; 746 } 747 if ((LocaleCompare("-help",argv[1]) == 0) || /* GNU standard option */ 748 (LocaleCompare("--help",argv[1]) == 0) ) { /* just a brief summary */ 749 if (cli_wand->wand.debug != MagickFalse) 750 (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(), 751 "- Special Option \"%s\"", argv[1]); 752 MagickUsage(MagickFalse); 753 goto Magick_Command_Exit; 754 } 755 if (LocaleCompare("-usage",argv[1]) == 0) { /* both version & usage */ 756 if (cli_wand->wand.debug != MagickFalse) 757 (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(), 758 "- Special Option \"%s\"", argv[1]); 759 CLIOption(cli_wand, "-version" ); 760 MagickUsage(MagickTrue); 761 goto Magick_Command_Exit; 762 } 763 } 764 765 /* not enough arguments -- including -help */ 766 if (argc < 3) { 767 (void) FormatLocaleFile(stderr, 768 "Error: Invalid argument or not enough arguments\n\n"); 769 MagickUsage(MagickFalse); 770 goto Magick_Command_Exit; 771 } 772 773 /* Special "concatenate option (hidden) for delegate usage */ 774 if (LocaleCompare("-concatenate",argv[1]) == 0) { 775 if (cli_wand->wand.debug != MagickFalse) 776 (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(), 777 "- Special Option \"%s\"", argv[1]); 778 ConcatenateImages(argc,argv,exception); 779 goto Magick_Command_Exit; 780 } 781 782 /* List Information and Abort */ 783 if (argc == 3 && LocaleCompare("-list",argv[1]) == 0) { 784 CLIOption(cli_wand, argv[1], argv[2]); 785 goto Magick_Command_Exit; 786 } 787 788 /* ------------- */ 789 /* The Main Call */ 790 791 if (LocaleCompare("-script",argv[1]) == 0) { 792 /* Start processing directly from script, no pre-script options 793 Replace wand command name with script name 794 First argument in the argv array is the script name to read. 795 */ 796 GetPathComponent(argv[2],TailPath,cli_wand->wand.name); 797 ProcessScriptOptions(cli_wand,argv[2],argc,argv,3); 798 } 799 else { 800 /* Normal Command Line, assumes output file as last option */ 801 ProcessCommandOptions(cli_wand,argc,argv,1); 802 } 803 /* ------------- */ 804 805 Magick_Command_Cleanup: 806 cli_wand->location="Cleanup"; 807 cli_wand->filename=argv[0]; 808 if (cli_wand->wand.debug != MagickFalse) 809 (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(), 810 "\"%s\"",argv[0]); 811 812 /* recover original image_info and clean up stacks 813 FUTURE: "-reset stacks" option */ 814 while ((cli_wand->image_list_stack != (Stack *) NULL) && 815 (cli_wand->image_list_stack->next != (Stack *) NULL)) 816 CLIOption(cli_wand,")"); 817 while ((cli_wand->image_info_stack != (Stack *) NULL) && 818 (cli_wand->image_info_stack->next != (Stack *) NULL)) 819 CLIOption(cli_wand,"}"); 820 821 /* assert we have recovered the original structures */ 822 assert(cli_wand->wand.image_info == image_info); 823 assert(cli_wand->wand.exception == exception); 824 825 /* Handle metadata for ImageMagickObject COM object for Windows VBS */ 826 if ((cli_wand->wand.images != (Image *) NULL) && 827 (metadata != (char **) NULL)) 828 { 829 const char 830 *format; 831 832 char 833 *text; 834 835 format="%w,%h,%m"; /* Get this from image_info Option splaytree */ 836 text=InterpretImageProperties(image_info,cli_wand->wand.images,format, 837 exception); 838 if (text == (char *) NULL) 839 ThrowMagickException(exception,GetMagickModule(),ResourceLimitError, 840 "MemoryAllocationFailed","`%s'", GetExceptionMessage(errno)); 841 else 842 { 843 (void) ConcatenateString(&(*metadata),text); 844 text=DestroyString(text); 845 } 846 } 847 848 Magick_Command_Exit: 849 cli_wand->location="Exiting"; 850 cli_wand->filename=argv[0]; 851 if (cli_wand->wand.debug != MagickFalse) 852 (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(), 853 "\"%s\"",argv[0]); 854 855 /* Destroy the special CLI Wand */ 856 cli_wand->wand.image_info = (ImageInfo *) NULL; /* not these */ 857 cli_wand->wand.exception = (ExceptionInfo *) NULL; 858 cli_wand=DestroyMagickCLI(cli_wand); 859 860 return(exception->severity < ErrorException ? MagickTrue : MagickFalse); 861 } 862