blob: 6ec30063a6e23396efc7f5a68cd0606f4cfe20c7 [file] [log] [blame]
anthonyfa1e43d2012-02-12 12:55:45 +00001/*
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
46/*
47 Include declarations.
48*/
49#include "MagickWand/studio.h"
50#include "MagickWand/MagickWand.h"
51#include "MagickWand/magick-wand-private.h"
anthony668f43a2012-02-20 14:55:32 +000052#include "MagickCore/memory_.h"
anthonyfa1e43d2012-02-12 12:55:45 +000053#include "MagickCore/string-private.h"
anthony668f43a2012-02-20 14:55:32 +000054#include "MagickWand/operation.h"
anthonyfa1e43d2012-02-12 12:55:45 +000055#include "MagickCore/utility-private.h"
anthony668f43a2012-02-20 14:55:32 +000056#include "MagickCore/version.h"
57
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
134typedef enum {
135 TokenStatusOK = 0,
136 TokenStatusEOF,
137 TokenStatusBadQuotes,
138 TokenStatusTokenTooBig,
139 TokenStatusBinary
140} TokenStatus;
141
142typedef 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
180static 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}
anthonyfa1e43d2012-02-12 12:55:45 +0000311
312/*
313%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
314% %
315% %
316% %
anthony668f43a2012-02-20 14:55:32 +0000317+ P r o c e s s S p e c i a l O p t i o n %
anthonyfa1e43d2012-02-12 12:55:45 +0000318% %
319% %
320% %
321%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
322%
anthony668f43a2012-02-20 14:55:32 +0000323% ProcessSpecialOption() Apply certian options that are specific to Shell
324% API interface. Specifically reading images and handling image and
325% image_info (settings) stacks.
anthonyfa1e43d2012-02-12 12:55:45 +0000326%
anthony668f43a2012-02-20 14:55:32 +0000327% The format of the ProcessSpecialOption method is:
anthonyfa1e43d2012-02-12 12:55:45 +0000328%
anthony668f43a2012-02-20 14:55:32 +0000329% void ProcessSpecialOption(MagickWand *wand,const char *option,
330% const char *arg, ProcessOptionFlags process_flags )
anthonyfa1e43d2012-02-12 12:55:45 +0000331%
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%
anthony668f43a2012-02-20 14:55:32 +0000340% o process_flags: Wether to process specific options or not.
341%
anthonyfa1e43d2012-02-12 12:55:45 +0000342*/
anthony668f43a2012-02-20 14:55:32 +0000343WandExport void ProcessSpecialOption(MagickWand *wand,
344 const char *option, const char *arg, ProcessOptionFlags process_flags)
anthonyfa1e43d2012-02-12 12:55:45 +0000345{
anthony668f43a2012-02-20 14:55:32 +0000346 if ( LocaleCompare("-read",option) == 0 )
anthonyfa1e43d2012-02-12 12:55:45 +0000347 {
anthonyfa1e43d2012-02-12 12:55:45 +0000348 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);
anthonyfa1e43d2012-02-12 12:55:45 +0000357 return;
358 }
anthony668f43a2012-02-20 14:55:32 +0000359 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
anthonyfa1e43d2012-02-12 12:55:45 +0000368#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
anthony668f43a2012-02-20 14:55:32 +0000376 // "region" "clone" "list" "version"
anthonyfa1e43d2012-02-12 12:55:45 +0000377 // It does not do "exit" however as due to its side-effect requirements
anthony668f43a2012-02-20 14:55:32 +0000378
379 if ( ( process_flags & ProcessUnknownOptionError ) != 0 )
380 MagickExceptionReturn(OptionError,"InvalidUseOfOption",option);
anthonyfa1e43d2012-02-12 12:55:45 +0000381#endif
382}
anthony668f43a2012-02-20 14:55:32 +0000383
anthonyfa1e43d2012-02-12 12:55:45 +0000384/*
385%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
386% %
387% %
388% %
anthony668f43a2012-02-20 14:55:32 +0000389+ P r o c e s s S c r i p t O p t i o n s %
anthonyfa1e43d2012-02-12 12:55:45 +0000390% %
391% %
392% %
393%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
394%
anthony668f43a2012-02-20 14:55:32 +0000395% 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.
anthonyfa1e43d2012-02-12 12:55:45 +0000398%
anthony668f43a2012-02-20 14:55:32 +0000399% 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.
anthonyfa1e43d2012-02-12 12:55:45 +0000401%
anthony668f43a2012-02-20 14:55:32 +0000402% 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)
anthonyfa1e43d2012-02-12 12:55:45 +0000411%
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*/
anthony668f43a2012-02-20 14:55:32 +0000421#define MagickExceptionScript(severity,tag,arg,line,col) \
anthonyfa1e43d2012-02-12 12:55:45 +0000422 (void) ThrowMagickException(wand->exception,GetMagickModule(),severity,tag, \
anthony668f43a2012-02-20 14:55:32 +0000423 "'%s' : Line %u Column %u of script \"%s\"", arg, line, col, wand->name);
424
425WandExport 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
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
anthonyfa1e43d2012-02-12 12:55:45 +0000698#define MagickExceptionReturn(severity,tag,option,arg) \
699{ \
700 MagickExceptionContinue(severity,tag,option,arg); \
701 return; \
702}
703
anthony668f43a2012-02-20 14:55:32 +0000704WandExport void ProcessCommandOptions(MagickWand *wand,int argc,
705 char **argv, ProcessOptionFlags process_flags )
anthonyfa1e43d2012-02-12 12:55:45 +0000706{
707 const char
708 *option,
709 *arg1,
710 *arg2;
711
712 MagickBooleanType
713 plus_alt_op;
714
715 ssize_t
716 i,
anthony668f43a2012-02-20 14:55:32 +0000717 end,
anthonyfa1e43d2012-02-12 12:55:45 +0000718 count;
719
720 CommandOptionFlags
anthony686b1a32012-02-15 14:50:53 +0000721 option_type;
anthonyfa1e43d2012-02-12 12:55:45 +0000722
anthony668f43a2012-02-20 14:55:32 +0000723 assert(argc>0 && argv[argc-1] != (char *)NULL);
anthonyfa1e43d2012-02-12 12:55:45 +0000724 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 */
anthony668f43a2012-02-20 14:55:32 +0000733 end = argc;
734 if ( ( process_flags & ProcessOutputFile ) != 0 )
735 end--;
736 for (i=0; i < end; i += count +1)
anthonyfa1e43d2012-02-12 12:55:45 +0000737 {
anthony668f43a2012-02-20 14:55:32 +0000738#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
anthonyfa1e43d2012-02-12 12:55:45 +0000745 option=argv[i];
746 plus_alt_op = MagickFalse;
747 arg1=(char *)NULL;
748 arg2=(char *)NULL;
749
anthony668f43a2012-02-20 14:55:32 +0000750
751 { const OptionInfo *option_info = GetCommandOptionInfo(argv[i]);
anthony686b1a32012-02-15 14:50:53 +0000752 count=option_info->type;
753 option_type=option_info->flags;
anthony668f43a2012-02-20 14:55:32 +0000754#if MagickCommandDebug >= 2
755 (void) FormatLocaleFile(stderr, "option \"%s\" matched \"%s\"\n",
756 argv[i], option_info->mnemonic );
anthony686b1a32012-02-15 14:50:53 +0000757#endif
758 }
anthonyfa1e43d2012-02-12 12:55:45 +0000759
anthony668f43a2012-02-20 14:55:32 +0000760 if ( option_type == UndefinedOptionFlag ||
761 (option_type & NonMagickOptionFlag) != 0 )
anthonyfa1e43d2012-02-12 12:55:45 +0000762 {
anthonyfa1e43d2012-02-12 12:55:45 +0000763#if MagickCommandDebug
anthony9724dd32012-02-12 13:08:34 +0000764 (void) FormatLocaleFile(stderr, "CLI Non-Option: \"%s\"\n", option);
anthonyfa1e43d2012-02-12 12:55:45 +0000765#endif
anthony668f43a2012-02-20 14:55:32 +0000766 if ( IsCommandOption(option) != MagickFalse )
anthonyfa1e43d2012-02-12 12:55:45 +0000767 {
anthony668f43a2012-02-20 14:55:32 +0000768 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 }
anthonyfa1e43d2012-02-12 12:55:45 +0000774 }
anthony668f43a2012-02-20 14:55:32 +0000775 else if ( ( process_flags & ProcessUnknownOptionError ) != 0 )
776 {
777 MagickExceptionReturn(OptionFatalError,"UnrecognizedOption",
778 option,i);
779 return;
780 }
781 continue;
anthonyfa1e43d2012-02-12 12:55:45 +0000782 }
783
anthony686b1a32012-02-15 14:50:53 +0000784 if ( (option_type & DeprecateOptionFlag) != 0 )
anthonyfa1e43d2012-02-12 12:55:45 +0000785 MagickExceptionContinue(OptionWarning,"DeprecatedOption",option,i);
786 /* continue processing option anyway */
787
anthony668f43a2012-02-20 14:55:32 +0000788 if ((i+count) >= end )
anthonyfa1e43d2012-02-12 12:55:45 +0000789 MagickExceptionReturn(OptionError,"MissingArgument",option,i);
anthony668f43a2012-02-20 14:55:32 +0000790
anthonyfa1e43d2012-02-12 12:55:45 +0000791 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
anthony668f43a2012-02-20 14:55:32 +0000796#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
anthony686b1a32012-02-15 14:50:53 +0000802 if ( (option_type & SpecialOptionFlag) != 0 )
anthonyfa1e43d2012-02-12 12:55:45 +0000803 {
anthony668f43a2012-02-20 14:55:32 +0000804 if ( ( process_flags & ProcessExitOption ) != 0
805 && LocaleCompare(option,"-exit") == 0 )
anthonyfa1e43d2012-02-12 12:55:45 +0000806 return;
anthony668f43a2012-02-20 14:55:32 +0000807 if ( ( process_flags & ProcessScriptOption ) != 0
808 && LocaleCompare(option,"-script") == 0)
anthonyfa1e43d2012-02-12 12:55:45 +0000809 {
810 // Unbalanced Parenthesis if stack not empty
anthony668f43a2012-02-20 14:55:32 +0000811 // Call Script, with a filename as a zeroth argument
812 ProcessScriptOptions(wand,argc-(i+1),argv+(i+1));
anthonyfa1e43d2012-02-12 12:55:45 +0000813 return;
814 }
anthony668f43a2012-02-20 14:55:32 +0000815 ProcessSpecialOption(wand,option,arg1,process_flags);
anthonyfa1e43d2012-02-12 12:55:45 +0000816 }
817
anthony686b1a32012-02-15 14:50:53 +0000818 if ( (option_type & SettingOptionFlags) != 0 )
anthonyfa1e43d2012-02-12 12:55:45 +0000819 {
820 WandSettingOptionInfo(wand, option+1, arg1);
821 // FUTURE: Sync Specific Settings into Images
822 }
823
anthony686b1a32012-02-15 14:50:53 +0000824 if ( (option_type & SimpleOperatorOptionFlag) != 0)
anthony668f43a2012-02-20 14:55:32 +0000825 WandSimpleOperatorImages(wand, plus_alt_op, option+1, arg1, arg2);
anthonyfa1e43d2012-02-12 12:55:45 +0000826
anthony686b1a32012-02-15 14:50:53 +0000827 if ( (option_type & ListOperatorOptionFlag) != 0 )
anthony668f43a2012-02-20 14:55:32 +0000828 WandListOperatorImages(wand, plus_alt_op, option+1, arg1, arg2);
anthonyfa1e43d2012-02-12 12:55:45 +0000829
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))
anthony668f43a2012-02-20 14:55:32 +0000836 return; /* FATAL - caller handles exception */
anthonyfa1e43d2012-02-12 12:55:45 +0000837 CatchException(wand->exception); /* output warnings and clear!!! */
838 }
839 }
840
anthony668f43a2012-02-20 14:55:32 +0000841 if ( ( process_flags & ProcessOutputFile ) == 0 )
842 return;
843 assert(end==argc-1);
844
845 /*
846 Write out final image!
847 */
848 option=argv[i];
anthony686b1a32012-02-15 14:50:53 +0000849
anthonyfa1e43d2012-02-12 12:55:45 +0000850#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
anthony668f43a2012-02-20 14:55:32 +0000857 /* This is a valid 'do no write' option for a CLI */
anthonyfa1e43d2012-02-12 12:55:45 +0000858 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)
anthony668f43a2012-02-20 14:55:32 +0000863 /* FUTURE: Better Error - Output Filename not Found */
864 MagickExceptionReturn(OptionError,"MissingOutputFilename",option,i);
anthonyfa1e43d2012-02-12 12:55:45 +0000865
anthony668f43a2012-02-20 14:55:32 +0000866 /* If no images in MagickWand */
anthonyfa1e43d2012-02-12 12:55:45 +0000867 if ( wand->images == (Image *) NULL )
868 {
anthony668f43a2012-02-20 14:55:32 +0000869 /* a "null:" output coder with no images is not an error! */
anthonyfa1e43d2012-02-12 12:55:45 +0000870 if ( LocaleCompare(option,"null:") == 0 )
871 return;
anthony668f43a2012-02-20 14:55:32 +0000872 MagickExceptionReturn(OptionError,"NoImagesForFinalWrite",option,i);
anthonyfa1e43d2012-02-12 12:55:45 +0000873 }
874
anthonyfa1e43d2012-02-12 12:55:45 +0000875 //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
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%
anthony668f43a2012-02-20 14:55:32 +0000893% 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()
anthonyfa1e43d2012-02-12 12:55:45 +0000899%
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
921static 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
960static 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
994WandExport 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 }
anthony668f43a2012-02-20 14:55:32 +00001019 /* The "magick" command must have at least two arguments */
anthonyfa1e43d2012-02-12 12:55:45 +00001020 if (argc < 3)
1021 return(MagickUsage());
1022 ReadCommandlLine(argc,&argv);
anthony668f43a2012-02-20 14:55:32 +00001023
anthonyfa1e43d2012-02-12 12:55:45 +00001024#if 0
anthony668f43a2012-02-20 14:55:32 +00001025 /* FUTURE: This does not make sense! Remove it.
1026 Only implied 'image read' needs to expand file name glob patterns
1027 */
anthonyfa1e43d2012-02-12 12:55:45 +00001028 status=ExpandFilenames(&argc,&argv);
1029 if (status == MagickFalse)
1030 ThrowConvertException(ResourceLimitError,"MemoryAllocationFailed",
1031 GetExceptionMessage(errno));
1032#endif
anthony686b1a32012-02-15 14:50:53 +00001033
anthony668f43a2012-02-20 14:55:32 +00001034 /* Special hidden option for 'delegates' - no wand needed */
anthonyfa1e43d2012-02-12 12:55:45 +00001035 if (LocaleCompare("-concatenate",argv[1]) == 0)
1036 return(ConcatenateImages(argc,argv,exception));
1037
anthony668f43a2012-02-20 14:55:32 +00001038 /* Initialize special "CLI Wand" to hold images and settings (empty) */
anthonyfa1e43d2012-02-12 12:55:45 +00001039 /* 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)
anthony668f43a2012-02-20 14:55:32 +00001049 /* Special option - list argument constants and other information */
1050 /* FUTURE - this really should be a direct MagickCore Function */
anthonyfa1e43d2012-02-12 12:55:45 +00001051 WandSettingOptionInfo(wand, argv[1]+1, argv[2]);
anthony686b1a32012-02-15 14:50:53 +00001052 else if (LocaleCompare("-script",argv[1]) == 0)
1053 {
1054 /* Start processing from script, no pre-script options */
anthony686b1a32012-02-15 14:50:53 +00001055 GetPathComponent(argv[2],TailPath,wand->name);
anthony668f43a2012-02-20 14:55:32 +00001056 ProcessScriptOptions(wand,argc-2,argv+2);
anthony686b1a32012-02-15 14:50:53 +00001057 }
anthonyfa1e43d2012-02-12 12:55:45 +00001058 else
anthony686b1a32012-02-15 14:50:53 +00001059 {
1060 /* Processing Command line, assuming output file as last option */
anthony668f43a2012-02-20 14:55:32 +00001061 ProcessCommandOptions(wand,argc-1,argv+1,MagickCommandOptionFlags);
anthony686b1a32012-02-15 14:50:53 +00001062 }
anthonyfa1e43d2012-02-12 12:55:45 +00001063
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}