]> 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-2012 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 "MagickCore/memory_.h"
53 #include "MagickCore/string-private.h"
54 #include "MagickWand/operation.h"
55 #include "MagickCore/utility-private.h"
56 #include "MagickCore/version.h"
57 \f
58 #define MagickCommandDebug 0
59 /*
60 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
61 %                                                                             %
62 %                                                                             %
63 %                                                                             %
64 %   G e t S c r i p t T o k e n                                               %
65 %                                                                             %
66 %                                                                             %
67 %                                                                             %
68 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
69 %
70 %  GetScriptToken() is fairly general, finite state token parser. That will
71 %  divide a input file stream into tokens, in a way that is almost identical
72 %  to a UNIX shell.
73 %
74 %  It returns 'MagickTrue' if a token was found. Even in the special case of
75 %  a empty token followed immediatally by a EOF. For example:  ''{EOF}
76 %
77 %  A token is returned immediatally the end of token is found.  That is
78 %  parsing is purely character by character, and not line-by-line. This
79 %  should allow for mixed stream of tokens (options), and other data (images)
80 %  without problems.  Assuming the other data has a well defined End-Of-Data
81 %  handling (see complex example below).
82 %
83 %  Tokens are white space separated, and may be quoted, or even partially
84 %  quoted by either single or double quotes, or the use of backslashes,
85 %  or any mix of the three.
86 %
87 %  For example:    This\ is' a 'single" token"
88 %
89 %  Single quotes will preserve all characters including backslashes. Double
90 %  quotes will also preserve backslashes unless escaping a double quote,
91 %  or another backslashes.  Other shell meta-characters are not treated as
92 %  special by this tokenizer.
93 %
94 %  For example Quoting the quote chars:
95 %              \'  "'"       \"  '"'  "\""      \\  '\'  "\\"
96 %
97 %  Comments start with a '#' character at the start of a new token (generally
98 %  at start of a line, or after a unquoted white space charcater) and continue
99 %  to the end of line.  The are simply ignored.  You can escape a comment '#'
100 %  character to return a token that starts with such a character.
101 %
102 %  More complex example...
103 %  Sending a PGM image in the middle of a standard input script.
104 %
105 %  magick -script - <<END
106 %    # read a stdin in-line image...
107 %    "pgm:-[0]" P2 2 2 3   0 1 1 2
108 %    # continue processing that image
109 %    -resize 100x100
110 %    -write enlarged.png
111 %  END
112 %
113 %  Only a single space character separates the 'image read' from the
114 %  'image data' after which the next operation is read.  This only works
115 %  for image data formats with a well defined length or End-of-Data marker
116 %  such as MIFF, and PbmPlus file formats.
117 %
118 %  The format of the MagickImageCommand method is:
119 %
120 %     MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
121 %
122 %  A description of each parameter follows:
123 %
124 %    o token_info    pointer to a structure holding token details
125 %
126 */
127
128 /* States of the parser */
129 #define IN_WHITE 0
130 #define IN_TOKEN 1
131 #define IN_QUOTE 2
132 #define IN_COMMENT 3
133
134 typedef enum {
135   TokenStatusOK = 0,
136   TokenStatusEOF,
137   TokenStatusBadQuotes,
138   TokenStatusTokenTooBig,
139   TokenStatusBinary
140 } TokenStatus;
141
142 typedef struct
143 {
144   FILE
145     *stream;        /* the file stream we are reading from */
146
147   char
148     *token;         /* array of characters to holding details of he token */
149
150   size_t
151     length,         /* length of token char array */
152     curr_line,      /* current location in script file */
153     curr_column,
154     token_line,     /* location of the start of this token */
155     token_column;
156
157   TokenStatus
158     status;         /* Have we reached EOF? see Token Status */
159 } ScriptTokenInfo;
160
161 /* macro to read character from stream */
162 #define GetChar(c) \
163 { \
164    c=fgetc(token_info->stream); \
165    token_info->curr_column++; \
166    if ( c == '\n' ) \
167      token_info->curr_line++, token_info->curr_column=0; \
168 }
169 /* macro to collect the token characters */
170 #define SaveChar(c) \
171 { \
172   if ((size_t) offset >= (token_info->length-1)) \
173     { token_info->token[offset++]='\0'; \
174       token_info->status=TokenStatusTokenTooBig; \
175       return(MagickFalse); \
176     } \
177   token_info->token[offset++]=(char) (c); \
178 }
179
180 static MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
181 {
182
183   int
184     quote,
185     c;
186
187   int
188     state;
189
190   ssize_t
191     offset;
192
193   /* EOF - no more tokens! */
194   if (token_info->status != TokenStatusOK)
195     {
196       token_info->token[0]='\0';
197       return(MagickFalse);
198     }
199
200   state=IN_WHITE;
201   quote='\0';
202   offset=0;
203   while(1)
204   {
205     /* get character */
206     GetChar(c);
207     if (c == '\0' || c == EOF)
208       break;
209
210     /* hash comment handling */
211     if ( state == IN_COMMENT )
212       { if ( c == '\n' )
213           state=IN_WHITE;
214         continue;
215       }
216     if (c == '#' && state == IN_WHITE)
217       state=IN_COMMENT;
218     /* whitespace break character */
219     if (strchr(" \n\r\t",c) != (char *)NULL)
220       {
221         switch (state)
222         {
223           case IN_TOKEN:
224             token_info->token[offset]='\0';
225             return(MagickTrue);
226           case IN_QUOTE:
227             SaveChar(c);
228             break;
229         }
230         continue;
231       }
232     /* quote character */
233     if (strchr("'\"",c) != (char *)NULL)
234       {
235         switch (state)
236         {
237           case IN_WHITE:
238             token_info->token_line=token_info->curr_line;
239             token_info->token_column=token_info->curr_column;
240           case IN_TOKEN:
241             state=IN_QUOTE;
242             quote=c;
243             break;
244           case IN_QUOTE:
245             if (c == quote)
246               {
247                 state=IN_TOKEN;
248                 quote='\0';
249               }
250             else
251               SaveChar(c);
252             break;
253         }
254         continue;
255       }
256     /* escape char (preserve in quotes - unless escaping the same quote) */
257     if (c == '\\')
258       {
259         if ( state==IN_QUOTE && quote == '\'' )
260           {
261             SaveChar('\\');
262             continue;
263           }
264         GetChar(c);
265         if (c == '\0' || c == EOF)
266           {
267             SaveChar('\\');
268             break;
269           }
270         switch (state)
271         {
272           case IN_WHITE:
273             token_info->token_line=token_info->curr_line;
274             token_info->token_column=token_info->curr_column;
275             state=IN_TOKEN;
276             break;
277           case IN_QUOTE:
278             if (c != quote && c != '\\')
279               SaveChar('\\');
280             break;
281         }
282         SaveChar(c);
283         continue;
284       }
285     /* ordinary character */
286     switch (state)
287     {
288       case IN_WHITE:
289         token_info->token_line=token_info->curr_line;
290         token_info->token_column=token_info->curr_column;
291         state=IN_TOKEN;
292       case IN_TOKEN:
293       case IN_QUOTE:
294         SaveChar(c);
295         break;
296       case IN_COMMENT:
297         break;
298     }
299   }
300   /* stream has EOF or produced a fatal error */
301   token_info->token[offset]='\0';
302   token_info->status = TokenStatusEOF;
303   if (state == IN_QUOTE)
304     token_info->status = TokenStatusBadQuotes;
305   if (c == '\0' )
306     token_info->status = TokenStatusBinary;
307   if (state == IN_TOKEN && token_info->status == TokenStatusEOF)
308     return(MagickTrue);
309   return(MagickFalse);
310 }
311 \f
312 /*
313 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
314 %                                                                             %
315 %                                                                             %
316 %                                                                             %
317 +   P r o c e s s S p e c i a l O p t i o n                                   %
318 %                                                                             %
319 %                                                                             %
320 %                                                                             %
321 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
322 %
323 %  ProcessSpecialOption() Apply certian options that are specific to Shell
324 %  API interface.  Specifically reading images and handling image and
325 %  image_info (settings) stacks.
326 %
327 %  The format of the ProcessSpecialOption method is:
328 %
329 %      void ProcessSpecialOption(MagickWand *wand,const char *option,
330 %           const char *arg, ProcessOptionFlags process_flags )
331 %
332 %  A description of each parameter follows:
333 %
334 %    o wand: the main CLI Wand to use.
335 %
336 %    o option: The special option (with any switch char) to process
337 %
338 %    o arg: Argument for option, if required
339 %
340 %    o process_flags: Wether to process specific options or not.
341 %
342 */
343 WandExport void ProcessSpecialOption(MagickWand *wand,
344      const char *option, const char *arg, ProcessOptionFlags process_flags)
345 {
346   if ( LocaleCompare("-read",option) == 0 )
347     {
348       Image *
349         new_images;
350
351       CopyMagickString(wand->image_info->filename,arg,MaxTextExtent);
352       if (wand->image_info->ping != MagickFalse)
353         new_images=PingImages(wand->image_info,wand->exception);
354       else
355         new_images=ReadImages(wand->image_info,wand->exception);
356       AppendImageToList(&wand->images, new_images);
357       return;
358     }
359   if (LocaleCompare("-sans",option) == 0)
360     return;
361   if (LocaleCompare("-sans0",option) == 0)
362     return;
363   if (LocaleCompare("-sans2",option) == 0)
364     return;
365   if (LocaleCompare("-noop",option) == 0)
366     return;
367
368 #if 0
369   if (LocaleCompare(option,"(") == 0)
370     // push images/settings
371   if (LocaleCompare(option,")") == 0)
372     // pop images/settings
373   if (LocaleCompare(option,"respect_parenthesis") == 0)
374     // adjust stack handling
375   // Other 'special' options this should handle
376   //    "region" "clone"  "list" "version"
377   // It does not do "exit" however as due to its side-effect requirements
378
379   if ( ( process_flags & ProcessUnknownOptionError ) != 0 )
380     MagickExceptionReturn(OptionError,"InvalidUseOfOption",option);
381 #endif
382 }
383 \f
384 /*
385 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
386 %                                                                             %
387 %                                                                             %
388 %                                                                             %
389 +   P r o c e s s S c r i p t O p t i o n s                                   %
390 %                                                                             %
391 %                                                                             %
392 %                                                                             %
393 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
394 %
395 %  ProcessScriptOptions() reads options and processes options as they are
396 %  found in the given file, or pipeline.  The filename to open and read
397 %  options is given as the zeroth argument of the argument array given.
398 %
399 %  A script not 'return' to the command line processing, nor can they
400 %  call (and return from) other scripts. At least not at this time.
401 %
402 %  However special script options may used to read and process the other
403 %  argument provided, typically those that followed a "-script" option on the
404 %  command line. These extra script arguments may be interpreted as being
405 %  images to read or write, settings (strings), or more options to be
406 %  processed.  How they are treated is up to the script being processed.
407 %
408 %  The format of the ProcessScriptOptions method is:
409 %
410 %    void ProcessScriptOptions(MagickWand *wand,int argc,char **argv)
411 %
412 %  A description of each parameter follows:
413 %
414 %    o wand: the main CLI Wand to use.
415 %
416 %    o argc: the number of elements in the argument vector.
417 %
418 %    o argv: A text array containing the command line arguments.
419 %
420 */
421 #define MagickExceptionScript(severity,tag,arg,line,col) \
422   (void) ThrowMagickException(wand->exception,GetMagickModule(),severity,tag, \
423        "'%s' : Line %u Column %u of script \"%s\"", arg, line, col, wand->name);
424
425 WandExport void ProcessScriptOptions(MagickWand *wand,int argc,
426      char **argv)
427 {
428   char
429     *option,
430     *arg1,
431     *arg2;
432
433   ssize_t
434     count;
435
436   size_t
437     option_line,       /* line and column of current option */
438     option_column;
439
440   CommandOptionFlags
441     option_type;
442
443   ScriptTokenInfo
444     token_info;
445
446   MagickBooleanType
447     plus_alt_op,
448     file_opened;
449
450   assert(argc>0 && argv[argc-1] != (char *)NULL);
451   assert(wand != (MagickWand *) NULL);
452   assert(wand->signature == WandSignature);
453   assert(wand->draw_info != (DrawInfo *) NULL); /* ensure it is a CLI wand */
454   if (wand->debug != MagickFalse)
455     (void) LogMagickEvent(WandEvent,GetMagickModule(),"%s",wand->name);
456
457   /* Initialize variables */
458   /* FUTURE handle file opening for '-' 'fd:N' or script filename */
459   file_opened=MagickFalse;
460   if ( LocaleCompare(argv[0],"-") == 0 )
461     {
462       CopyMagickString(wand->name,"stdin",MaxTextExtent);
463       token_info.stream=stdin;
464       file_opened=MagickFalse;
465     }
466   else
467     {
468       GetPathComponent(argv[0],TailPath,wand->name);
469       token_info.stream=fopen(argv[0], "r");
470       file_opened=MagickTrue;
471     }
472
473   option = arg1 = arg2 = (char*)NULL;
474   token_info.curr_line=1;
475   token_info.curr_column=0;
476   token_info.status=TokenStatusOK;
477   token_info.length=MaxTextExtent;
478   token_info.token=(char *) AcquireQuantumMemory(MaxTextExtent,sizeof(char));
479   if (token_info.token == (char *) NULL)
480     {
481       if ( file_opened != MagickFalse )
482         fclose(token_info.stream);
483       MagickExceptionScript(ResourceLimitError,"MemoryAllocationFailed","",0,0);
484       (void) ThrowMagickException(wand->exception,GetMagickModule(),
485            ResourceLimitError,"MemoryAllocationFailed","script token buffer");
486       return;
487     }
488
489   /* Process Options from Script */
490   while (1)
491     {
492       /* Get a option */
493       if( GetScriptToken(&token_info) == MagickFalse )
494         break;
495
496       /* option length sanity check */
497       if( strlen(token_info.token) > 40 )
498         { token_info.token[37] = '.';
499           token_info.token[38] = '.';
500           token_info.token[39] = '.';
501           token_info.token[40] = '\0';
502           MagickExceptionScript(OptionFatalError,"UnrecognizedOption",
503                token_info.token,token_info.token_line,token_info.token_column);
504           break;
505         }
506
507       /* save option details */
508       CloneString(&option,token_info.token);
509       option_line=token_info.token_line;
510       option_column=token_info.token_column;
511
512 #if MagickCommandDebug
513       (void) FormatLocaleFile(stderr, "Script Option Token: %u,%u: \"%s\"\n",
514                option_line, option_column, option );
515 #endif
516       /* get option type and argument count */
517       { const OptionInfo *option_info = GetCommandOptionInfo(option);
518         count=option_info->type;
519         option_type=option_info->flags;
520 #if MagickCommandDebug >= 2
521         (void) FormatLocaleFile(stderr, "option \"%s\" matched \"%s\"\n",
522              option, option_info->mnemonic );
523 #endif
524       }
525
526       /* handle a undefined option - image read? */
527       if ( option_type == UndefinedOptionFlag ||
528            (option_type & NonMagickOptionFlag) != 0 )
529         {
530 #if MagickCommandDebug
531           (void) FormatLocaleFile(stderr, "Script Non-Option: \"%s\"\n", option);
532 #endif
533           if ( IsCommandOption(option) == MagickFalse)
534             {
535               /* non-option -- treat as a image read */
536               ProcessSpecialOption(wand,"-read",option,MagickScriptReadFlags);
537               count = 0;
538               continue;
539             }
540           MagickExceptionScript(OptionFatalError,"UnrecognizedOption",
541                option,option_line,option_column);
542           break;
543         }
544
545       plus_alt_op = MagickFalse;
546       if (*option=='+') plus_alt_op = MagickTrue;
547
548       if ( count >= 1 )
549         {
550           if( GetScriptToken(&token_info) == MagickFalse )
551             {
552               MagickExceptionScript(OptionError,"MissingArgument",option,
553                  option_line,option_column);
554               break;
555             }
556           CloneString(&arg1,token_info.token);
557         }
558       else
559         CloneString(&arg1,(*option!='+')?"true":(char *)NULL);
560
561       if ( count >= 2 )
562         {
563           if( GetScriptToken(&token_info) == MagickFalse )
564             {
565               MagickExceptionScript(OptionError,"MissingArgument",option,
566                  option_line,option_column);
567               break;
568             }
569           CloneString(&arg2,token_info.token);
570         }
571       else
572         CloneString(&arg2,(char *)NULL);
573
574       /* handle script special options */
575       //either continue processing command line
576       // or making use of the command line options.
577       //ProcessCommandOptions(wand,count+1,argv, MagickScriptArgsFlags);
578
579 #if MagickCommandDebug
580       (void) FormatLocaleFile(stderr,
581           "Script Option: \"%s\" \tCount: %d  Flags: %04x  Args: \"%s\" \"%s\"\n",
582           option,(int) count,option_type,arg1,arg2);
583 #endif
584
585       /* Process non-script specific option from file */
586       if ( (option_type & SpecialOptionFlag) != 0 )
587         {
588           if ( LocaleCompare(option,"-exit") == 0 )
589             break;
590           /* No "-script" from script at this time */
591           ProcessSpecialOption(wand,option,arg1,MagickScriptReadFlags);
592         }
593
594       if ( (option_type & SettingOptionFlags) != 0 )
595         {
596           WandSettingOptionInfo(wand, option+1, arg1);
597           // FUTURE: Sync Specific Settings into Images
598         }
599
600       if ( (option_type & SimpleOperatorOptionFlag) != 0)
601         WandSimpleOperatorImages(wand, plus_alt_op, option+1, arg1, arg2);
602
603       if ( (option_type & ListOperatorOptionFlag) != 0 )
604         WandListOperatorImages(wand, plus_alt_op, option+1, arg1, arg2);
605
606       // FUTURE: '-regard_warning' causes IM to exit more prematurely!
607       // Note pipelined options may like more control over this level
608       if (wand->exception->severity > ErrorException)
609         {
610           if (wand->exception->severity > ErrorException)
611               //(regard_warnings != MagickFalse))
612             break;                     /* FATAL - caller handles exception */
613           CatchException(wand->exception); /* output warnings and clear!!! */
614         }
615     }
616 #if MagickCommandDebug
617   (void) FormatLocaleFile(stderr, "Script End: %d\n", token_info.status);
618 #endif
619   /* token sanity for error report */
620   if( strlen(token_info.token) > 40 )
621     { token_info.token[37] = '.';
622       token_info.token[38] = '.';
623       token_info.token[39] = '.';
624       token_info.token[40] = '\0';
625     }
626
627    switch( token_info.status )
628     {
629       case TokenStatusBadQuotes:
630         MagickExceptionScript(OptionFatalError,"ScriptUnbalancedQuotes",
631              token_info.token,token_info.token_line,token_info.token_column);
632         break;
633       case TokenStatusTokenTooBig:
634         MagickExceptionScript(OptionFatalError,"ScriptTokenTooBig",
635              token_info.token,token_info.token_line,token_info.token_column);
636         break;
637       case TokenStatusBinary:
638         MagickExceptionScript(OptionFatalError,"ScriptIsBinary","",
639              token_info.curr_line,token_info.curr_column);
640         break;
641       case TokenStatusOK:
642       case TokenStatusEOF:
643         break;
644     }
645
646    /* Clean up */
647    if ( file_opened != MagickFalse )
648      fclose(token_info.stream);
649
650    CloneString(&option,(char *)NULL);
651    CloneString(&arg1,(char *)NULL);
652    CloneString(&arg2,(char *)NULL);
653
654    return;
655 }
656 \f
657 /*
658 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
659 %                                                                             %
660 %                                                                             %
661 %                                                                             %
662 +  P r o c e s s C o m m a n d O p t i o n s                                  %
663 %                                                                             %
664 %                                                                             %
665 %                                                                             %
666 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
667 %
668 %  ProcessCommandOptions() reads and processes arguments in the given
669 %  command line argument array. The array does not contain the command
670 %  being processed, only the options.
671 %
672 %  The 'process_flags' can be used to control and limit option processing.
673 %  For example, to only process one option, or how unknown and special options
674 %  are to be handled, and if the last argument in array is to be regarded as a
675 %  final image write argument (filename or special coder).
676 %
677 %  The format of the ProcessCommandOptions method is:
678 %
679 %    int ProcessCommandOptions(MagickWand *wand,int argc,char **argv,
680 %           int *index, ProcessOptionFlags process_flags )
681 %
682 %  A description of each parameter follows:
683 %
684 %    o wand: the main CLI Wand to use.
685 %
686 %    o argc: the number of elements in the argument vector.
687 %
688 %    o argv: A text array containing the command line arguments.
689 %
690 %    o process_flags: What type of arguments we are allowed to process
691 %
692 */
693 /* FUTURE: correctly identify option... CLI arg,  Script line,column  */
694 #define MagickExceptionContinue(severity,tag,arg,index) \
695   (void) ThrowMagickException(wand->exception,GetMagickModule(),severity,tag, \
696        "'%s' : CLI Arg #%d", arg, (int) index); \
697
698 #define MagickExceptionReturn(severity,tag,option,arg) \
699 { \
700   MagickExceptionContinue(severity,tag,option,arg); \
701   return; \
702 }
703
704 WandExport void ProcessCommandOptions(MagickWand *wand,int argc,
705      char **argv, ProcessOptionFlags process_flags )
706 {
707   const char
708     *option,
709     *arg1,
710     *arg2;
711
712   MagickBooleanType
713     plus_alt_op;
714
715   ssize_t
716     i,
717     end,
718     count;
719
720   CommandOptionFlags
721     option_type;
722
723   assert(argc>0 && argv[argc-1] != (char *)NULL);
724   assert(wand != (MagickWand *) NULL);
725   assert(wand->signature == WandSignature);
726   assert(wand->draw_info != (DrawInfo *) NULL); /* ensure it is a CLI wand */
727   if (wand->debug != MagickFalse)
728     (void) LogMagickEvent(WandEvent,GetMagickModule(),"%s",wand->name);
729
730   /*
731     Parse command-line options.
732   */
733   end = argc;
734   if ( ( process_flags & ProcessOutputFile ) != 0 )
735     end--;
736   for (i=0; i < end; i += count +1)
737     {
738 #if MagickCommandDebug >= 2
739       (void) FormatLocaleFile(stderr, "index= %d\n", i );
740 #endif
741       /* Finished processing one option? */
742       if ( ( process_flags & ProcessOneOptionOnly ) != 0 && i != 0 )
743         return;
744
745       option=argv[i];
746       plus_alt_op = MagickFalse;
747       arg1=(char *)NULL;
748       arg2=(char *)NULL;
749
750
751       { const OptionInfo *option_info = GetCommandOptionInfo(argv[i]);
752         count=option_info->type;
753         option_type=option_info->flags;
754 #if MagickCommandDebug >= 2
755         (void) FormatLocaleFile(stderr, "option \"%s\" matched \"%s\"\n",
756              argv[i], option_info->mnemonic );
757 #endif
758       }
759
760       if ( option_type == UndefinedOptionFlag ||
761            (option_type & NonMagickOptionFlag) != 0 )
762         {
763 #if MagickCommandDebug
764           (void) FormatLocaleFile(stderr, "CLI Non-Option: \"%s\"\n", option);
765 #endif
766           if ( IsCommandOption(option) != MagickFalse )
767             {
768               if ( ( process_flags & ProcessNonOptionImageRead ) != 0 )
769                {
770                 /* non-option -- treat as a image read */
771                 ProcessSpecialOption(wand,"-read",option,process_flags);
772                 count = 0;
773               }
774             }
775           else if ( ( process_flags & ProcessUnknownOptionError ) != 0 )
776             {
777               MagickExceptionReturn(OptionFatalError,"UnrecognizedOption",
778                    option,i);
779               return;
780             }
781           continue;
782         }
783
784       if ( (option_type & DeprecateOptionFlag) != 0 )
785         MagickExceptionContinue(OptionWarning,"DeprecatedOption",option,i);
786         /* continue processing option anyway */
787
788       if ((i+count) >= end )
789         MagickExceptionReturn(OptionError,"MissingArgument",option,i);
790
791       if (*option=='+') plus_alt_op = MagickTrue;
792       if (*option!='+') arg1 = "true";
793       if ( count >= 1 ) arg1 = argv[i+1];
794       if ( count >= 2 ) arg2 = argv[i+2];
795
796 #if MagickCommandDebug
797       (void) FormatLocaleFile(stderr,
798           "CLI Option: \"%s\" \tCount: %d  Flags: %04x  Args: \"%s\" \"%s\"\n",
799           option,(int) count,option_type,arg1,arg2);
800 #endif
801
802       if ( (option_type & SpecialOptionFlag) != 0 )
803         {
804           if ( ( process_flags & ProcessExitOption ) != 0
805                && LocaleCompare(option,"-exit") == 0 )
806             return;
807           if ( ( process_flags & ProcessScriptOption ) != 0
808                && LocaleCompare(option,"-script") == 0)
809             {
810               // Unbalanced Parenthesis if stack not empty
811               // Call Script, with a filename as a zeroth argument
812               ProcessScriptOptions(wand,argc-(i+1),argv+(i+1));
813               return;
814             }
815           ProcessSpecialOption(wand,option,arg1,process_flags);
816         }
817
818       if ( (option_type & SettingOptionFlags) != 0 )
819         {
820           WandSettingOptionInfo(wand, option+1, arg1);
821           // FUTURE: Sync Specific Settings into Images
822         }
823
824       if ( (option_type & SimpleOperatorOptionFlag) != 0)
825         WandSimpleOperatorImages(wand, plus_alt_op, option+1, arg1, arg2);
826
827       if ( (option_type & ListOperatorOptionFlag) != 0 )
828         WandListOperatorImages(wand, plus_alt_op, option+1, arg1, arg2);
829
830       // FUTURE: '-regard_warning' causes IM to exit more prematurely!
831       // Note pipelined options may like more control over this level
832       if (wand->exception->severity > ErrorException)
833         {
834           if (wand->exception->severity > ErrorException)
835               //(regard_warnings != MagickFalse))
836             return;                    /* FATAL - caller handles exception */
837           CatchException(wand->exception); /* output warnings and clear!!! */
838         }
839     }
840
841   if ( ( process_flags & ProcessOutputFile ) == 0 )
842     return;
843   assert(end==argc-1);
844
845   /*
846      Write out final image!
847   */
848   option=argv[i];
849
850 #if MagickCommandDebug
851   (void) FormatLocaleFile(stderr, "CLI Output: \"%s\"\n", option );
852 #endif
853
854   // if stacks are not empty
855   //  ThrowConvertException(OptionError,"UnbalancedParenthesis",option,i);
856
857   /* This is a valid 'do no write' option for a CLI */
858   if (LocaleCompare(option,"-exit") == 0 )
859     return;  /* just exit, no image write */
860
861   /* If there is an option -- produce an error */
862   if (IsCommandOption(option) != MagickFalse)
863     /* FUTURE: Better Error - Output Filename not Found */
864     MagickExceptionReturn(OptionError,"MissingOutputFilename",option,i);
865
866   /* If no images in MagickWand */
867   if ( wand->images == (Image *) NULL )
868     {
869       /* a "null:" output coder with no images is not an error! */
870       if ( LocaleCompare(option,"null:") == 0 )
871         return;
872       MagickExceptionReturn(OptionError,"NoImagesForFinalWrite",option,i);
873     }
874
875   //WandListOperatorImages(wand,MagickFalse,"write",option,(const char *)NULL);
876   (void) SyncImagesSettings(wand->image_info,wand->images,wand->exception);
877   (void) WriteImages(wand->image_info,wand->images,option,wand->exception);
878
879   return;
880 }
881 \f
882 /*
883 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
884 %                                                                             %
885 %                                                                             %
886 %                                                                             %
887 +   M a g i c k I m a g e C o m m a n d                                       %
888 %                                                                             %
889 %                                                                             %
890 %                                                                             %
891 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
892 %
893 %  MagickImageCommand() Handle special use CLI arguments and prepare a
894 %  CLI MagickWand to process the command line or directly specified script.
895 %
896 %  This is essentualy interface function between the MagickCore library
897 %  initialization function MagickCommandGenesis(), and the option MagickWand
898 %  processing functions  ProcessCommandOptions()  or  ProcessScriptOptions()
899 %
900 %  The format of the MagickImageCommand method is:
901 %
902 %      MagickBooleanType MagickImageCommand(ImageInfo *image_info,
903 %           int argc, char **argv, char **metadata, ExceptionInfo *exception)
904 %
905 %  A description of each parameter follows:
906 %
907 %    o image_info: the starting image_info structure
908 %         (for compatibilty with MagickCommandGenisis())
909 %
910 %    o argc: the number of elements in the argument vector.
911 %
912 %    o argv: A text array containing the command line arguments.
913 %
914 %    o metadata: any metadata is returned here.
915 %         (for compatibilty with MagickCommandGenisis())
916 %
917 %    o exception: return any errors or warnings in this structure.
918 %
919 */
920
921 static MagickBooleanType MagickUsage(void)
922 {
923   printf("Version: %s\n",GetMagickVersion((size_t *) NULL));
924   printf("Copyright: %s\n",GetMagickCopyright());
925   printf("Features: %s\n\n",GetMagickFeatures());
926   printf("\n");
927
928   printf("Usage: %s [(options|images) ...] output_image\n", GetClientName());
929   printf("       %s -script filename [script args...]\n", GetClientName());
930   printf("       ... | %s -script - | ...\n", GetClientName());
931   printf("\n");
932
933   printf("  For more information on usage, options, examples, and technqiues\n");
934   printf("  see the ImageMagick website at\n    %s\n", MagickAuthoritativeURL);
935   printf("  Or the web pages in ImageMagick Sources\n");
936   return(MagickFalse);
937 }
938
939 /*
940    Concatanate given file arguments to the given output argument.
941    Used for a special -concatenate option used for specific 'delegates'.
942    The option is not formally documented.
943
944       magick -concatenate files... output
945
946    This is much like the UNIX "cat" command, but for both UNIX and Windows,
947    however the last argument provides the output filename.
948 */
949 #define ThrowFileException(exception,severity,tag,context) \
950 { \
951   char \
952     *message; \
953  \
954   message=GetExceptionMessage(errno); \
955   (void) ThrowMagickException(exception,GetMagickModule(),severity, \
956     tag == (const char *) NULL ? "unknown" : tag,"`%s': %s",context,message); \
957   message=DestroyString(message); \
958 }
959
960 static MagickBooleanType ConcatenateImages(int argc,char **argv,
961   ExceptionInfo *exception)
962 {
963   FILE
964     *input,
965     *output;
966
967   int
968     c;
969
970   register ssize_t
971     i;
972
973   output=fopen_utf8(argv[argc-1],"wb");
974   if (output == (FILE *) NULL)
975     {
976       ThrowFileException(exception,FileOpenError,"UnableToOpenFile",
977         argv[argc-1]);
978       return(MagickFalse);
979     }
980   for (i=2; i < (ssize_t) (argc-1); i++)
981   {
982     input=fopen_utf8(argv[i],"rb");
983     if (input == (FILE *) NULL)
984       ThrowFileException(exception,FileOpenError,"UnableToOpenFile",argv[i]);
985     for (c=fgetc(input); c != EOF; c=fgetc(input))
986       (void) fputc((char) c,output);
987     (void) fclose(input);
988     (void) remove_utf8(argv[i]);
989   }
990   (void) fclose(output);
991   return(MagickTrue);
992 }
993
994 WandExport MagickBooleanType MagickImageCommand(ImageInfo *image_info,
995   int argc,char **argv,char **metadata,ExceptionInfo *exception)
996 {
997   MagickWand
998     *wand;
999
1000   const char
1001     *option;
1002
1003   /* Handle special single use options */
1004   if (argc == 2)
1005     {
1006       option=argv[1];
1007       if ((LocaleCompare("-version",option+1) == 0) ||
1008           (LocaleCompare("--version",option+1) == 0) )
1009         {
1010           (void) FormatLocaleFile(stdout,"Version: %s\n",
1011             GetMagickVersion((size_t *) NULL));
1012           (void) FormatLocaleFile(stdout,"Copyright: %s\n",
1013             GetMagickCopyright());
1014           (void) FormatLocaleFile(stdout,"Features: %s\n\n",
1015             GetMagickFeatures());
1016           return(MagickFalse);
1017         }
1018     }
1019   /* The "magick" command must have at least two arguments */
1020   if (argc < 3)
1021     return(MagickUsage());
1022   ReadCommandlLine(argc,&argv);
1023
1024 #if 0
1025   /* FUTURE: This does not make sense!  Remove it.
1026      Only implied 'image read' needs to expand file name glob patterns
1027   */
1028   status=ExpandFilenames(&argc,&argv);
1029   if (status == MagickFalse)
1030     ThrowConvertException(ResourceLimitError,"MemoryAllocationFailed",
1031       GetExceptionMessage(errno));
1032 #endif
1033
1034   /* Special hidden option for 'delegates' - no wand needed */
1035   if (LocaleCompare("-concatenate",argv[1]) == 0)
1036     return(ConcatenateImages(argc,argv,exception));
1037
1038   /* Initialize special "CLI Wand" to hold images and settings (empty) */
1039   /* FUTURE: add this to 'operations.c' */
1040   wand=NewMagickWand();
1041   wand->image_info=DestroyImageInfo(wand->image_info);
1042   wand->image_info=image_info;
1043   wand->exception=DestroyExceptionInfo(wand->exception);
1044   wand->exception=exception;
1045   wand->draw_info=CloneDrawInfo(image_info,(DrawInfo *) NULL);
1046   wand->quantize_info=AcquireQuantizeInfo(image_info);
1047
1048   if (LocaleCompare("-list",argv[1]) == 0)
1049     /* Special option - list argument constants and other information */
1050     /* FUTURE - this really should be a direct MagickCore Function */
1051     WandSettingOptionInfo(wand, argv[1]+1, argv[2]);
1052   else if (LocaleCompare("-script",argv[1]) == 0)
1053     {
1054       /* Start processing from script, no pre-script options */
1055       GetPathComponent(argv[2],TailPath,wand->name);
1056       ProcessScriptOptions(wand,argc-2,argv+2);
1057     }
1058   else
1059     {
1060       /* Processing Command line, assuming output file as last option */
1061       ProcessCommandOptions(wand,argc-1,argv+1,MagickCommandOptionFlags);
1062     }
1063
1064   assert(wand->exception == exception);
1065   assert(wand->image_info == image_info);
1066
1067   /* Handle metadata for ImageMagickObject COM object for Windows VBS */
1068   if (metadata != (char **) NULL)
1069     {
1070       const char
1071         *format;
1072
1073       char
1074         *text;
1075
1076       format="%w,%h,%m";   // Get this from image_info Option splaytree
1077
1078       text=InterpretImageProperties(image_info,wand->images,format,exception);
1079       if (text == (char *) NULL)
1080         ThrowMagickException(exception,GetMagickModule(),ResourceLimitError,
1081              "MemoryAllocationFailed","`%s'", GetExceptionMessage(errno));
1082       else
1083         {
1084           (void) ConcatenateString(&(*metadata),text);
1085           text=DestroyString(text);
1086         }
1087     }
1088
1089   /* Destroy the special CLI Wand */
1090   wand->exception = (ExceptionInfo *)NULL;
1091   wand->image_info = (ImageInfo *)NULL;
1092   wand=DestroyMagickWand(wand);
1093
1094   return((exception->severity > ErrorException) ? MagickFalse : MagickTrue);
1095 }