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