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