2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5 % SSS CCC RRRR III PPPP TTTTT TTTTT OOO K K EEEE N N %
6 % S C R R I P P T T O O K K E NN N %
7 % SSS C RRRR I PPPP T T O O KK EEE N N N %
8 % S C R R I P T T O O K K E N NN %
9 % SSSS CCC R RR III P T T OOO K K EEEE N N %
11 % Tokenize Magick Script into Options %
18 % Copyright 1999-2018 ImageMagick Studio LLC, a non-profit organization %
19 % dedicated to making software imaging solutions freely available. %
21 % You may not use this file except in compliance with the License. You may %
22 % obtain a copy of the License at %
24 % https://www.imagemagick.org/script/license.php %
26 % Unless required by applicable law or agreed to in writing, software %
27 % distributed under the License is distributed on an "AS IS" BASIS, %
28 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
29 % See the License for the specific language governing permissions and %
30 % limitations under the License. %
32 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
34 % Read a stream of characters and return tokens one at a time.
36 % The input stream is dived into individual 'tokens' (representing 'words' or
37 % 'options'), in a way that is as close to a UNIX shell, as is feasable.
38 % Only shell variable, and command substitutions will not be performed.
39 % Tokens can be any length.
41 % The main function call is GetScriptToken() (see below) whcih returns one
42 % and only one token at a time. The other functions provide support to this
43 % function, opening scripts, and seting up the required structures.
45 % More specifically...
47 % Tokens are white space separated, and may be quoted, or even partially
48 % quoted by either single or double quotes, or the use of backslashes,
49 % or any mix of the three.
51 % For example: This\ is' a 'single" token"
53 % A token is returned immediatally the end of token is found. That is as soon
54 % as a unquoted white-space or EOF condition has been found. That is to say
55 % the file stream is parsed purely character-by-character, regardless any
56 % buffering constraints set by the system. It is not parsed line-by-line.
58 % The function will return 'MagickTrue' if a valid token was found, while
59 % the token status will be set accordingally to 'OK' or 'EOF', according to
60 % the cause of the end of token. The token may be an empty string if the
61 % input was a quoted empty string. Other error conditions return a value of
62 % MagickFalse, indicating any token found but was incomplete due to some
65 % Single quotes will preserve all characters including backslashes. Double
66 % quotes will also preserve backslashes unless escaping a double quote,
67 % or another backslashes. Other shell meta-characters are not treated as
68 % special by this tokenizer.
70 % For example Quoting the quote chars:
71 % \' "'" \" '"' "\"" \\ '\' "\\"
73 % Outside quotes, backslash characters will make spaces, tabs and quotes part
74 % of a token returned. However a backslash at the end of a line (and outside
75 % quotes) will cause the newline to be completely ignored (as per the shell
78 % Comments start with a '#' character at the start of a new token, will be
79 % completely ignored upto the end of line, regardless of any backslash at the
80 % end of the line. You can escape a comment '#', using quotes or backlsashes
81 % just as you can in a shell.
83 % The parser will accept both newlines, returns, or return-newlines to mark
84 % the EOL. Though this is technically breaking (or perhaps adding to) the
85 % 'BASH' syntax that is being followed.
88 % UNIX script Launcher...
90 % The use of '#' comments allow normal UNIX 'scripting' to be used to call on
91 % the "magick" command to parse the tokens from a file
93 % #!/path/to/command/magick -script
96 % UNIX 'env' command launcher...
98 % If "magick" is renamed "magick-script" you can use a 'env' UNIX launcher
100 % #!/usr/bin/env magick-script
103 % Shell script launcher...
105 % As a special case a ':' at the start of a line is also treated as a comment
106 % This allows a magick script to ignore a line that can be parsed by the shell
107 % and not by the magick script (tokenizer). This allows for an alternative
108 % script 'launcher' to be used for magick scripts.
111 % :; exec magick -script "$0" "$@"; exit 10
113 % # The rest of the file is magick script
114 % -read label:"This is a Magick Script!"
117 % Or with some shell pre/post processing...
120 % :; echo "This part is run in the shell, but ignored by Magick"
121 % :; magick -script "$0" "$@"
122 % :; echo "This is run after the "magick" script is finished!"
125 % # The rest of the file is magick script
126 % -read label:"This is a Magick Script!"
130 % DOS script launcher...
132 % Similarly any '@' at the start of the line (outside of quotes) will also be
133 % treated as comment. This allow you to create a DOS script launcher, to
134 % allow a ".bat" DOS scripts to run as "magick" scripts instead.
136 % @echo This line is DOS executed but ignored by Magick
137 % @magick -script %~dpnx0 %*
138 % @echo This line is processed after the Magick script is finished
141 % # The rest of the file is magick script
142 % -read label:"This is a Magick Script!"
145 % But this can also be used as a shell script launcher as well!
146 % Though is more restrictive and less free-form than using ':'.
149 % @() { exec magick -script "$@"; }
152 % # The rest of the file is magick script
153 % -read label:"This is a Magick Script!"
156 % Or even like this...
160 % @; exec magick -script "$0" "$@"; exit
162 % # The rest of the file is magick script
163 % -read label:"This is a Magick Script!"
169 Include declarations.
171 NOTE: Do not include if being compiled into the "test/script-token-test.c"
172 module, for low level token testing.
174 #ifndef SCRIPT_TOKEN_TESTING
175 # include "MagickWand/studio.h"
176 # include "MagickWand/MagickWand.h"
177 # include "MagickWand/script-token.h"
178 # include "MagickCore/string-private.h"
179 # include "MagickCore/utility-private.h"
183 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187 % A c q u i r e S c r i p t T o k e n I n f o %
191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
193 % AcquireScriptTokenInfo() allocated, initializes and opens the given
194 % file stream from which tokens are to be extracted.
196 % The format of the AcquireScriptTokenInfo method is:
198 % ScriptTokenInfo *AcquireScriptTokenInfo(char *filename)
200 % A description of each parameter follows:
202 % o filename the filename to open ("-" means stdin)
205 WandExport ScriptTokenInfo *AcquireScriptTokenInfo(const char *filename)
210 token_info=(ScriptTokenInfo *) AcquireMagickMemory(sizeof(*token_info));
211 if (token_info == (ScriptTokenInfo *) NULL)
213 (void) memset(token_info,0,sizeof(*token_info));
215 token_info->opened=MagickFalse;
216 if ( LocaleCompare(filename,"-") == 0 ) {
217 token_info->stream=stdin;
218 token_info->opened=MagickFalse;
220 else if ( LocaleNCompare(filename,"fd:",3) == 0 ) {
221 token_info->stream=fdopen(StringToLong(filename+3),"r");
222 token_info->opened=MagickFalse;
225 token_info->stream=fopen_utf8(filename, "r");
227 if ( token_info->stream == (FILE *) NULL ) {
228 token_info=(ScriptTokenInfo *) RelinquishMagickMemory(token_info);
232 token_info->curr_line=1;
233 token_info->length=INITAL_TOKEN_LENGTH;
234 token_info->token=(char *) AcquireMagickMemory(token_info->length);
236 token_info->status=(token_info->token != (char *) NULL)
237 ? TokenStatusOK : TokenStatusMemoryFailed;
238 token_info->signature=MagickWandSignature;
244 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
248 % D e s t r o y S c r i p t T o k e n I n f o %
252 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
254 % DestroyScriptTokenInfo() allocated, initializes and opens the given
255 % file stream from which tokens are to be extracted.
257 % The format of the DestroyScriptTokenInfo method is:
259 % ScriptTokenInfo *DestroyScriptTokenInfo(ScriptTokenInfo *token_info)
261 % A description of each parameter follows:
263 % o token_info The ScriptTokenInfo structure to be destroyed
266 WandExport ScriptTokenInfo * DestroyScriptTokenInfo(ScriptTokenInfo *token_info)
268 assert(token_info != (ScriptTokenInfo *) NULL);
269 assert(token_info->signature == MagickWandSignature);
271 if ( token_info->opened != MagickFalse )
272 fclose(token_info->stream);
274 if (token_info->token != (char *) NULL )
275 token_info->token=(char *) RelinquishMagickMemory(token_info->token);
276 token_info=(ScriptTokenInfo *) RelinquishMagickMemory(token_info);
281 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
285 % G e t S c r i p t T o k e n %
289 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
291 % GetScriptToken() a fairly general, finite state token parser. That returns
292 % tokens one at a time, as soon as posible.
295 % The format of the GetScriptToken method is:
297 % MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
299 % A description of each parameter follows:
301 % o token_info pointer to a structure holding token details
304 /* States of the parser */
310 /* Macro to read character from stream
312 This also keeps track of the line and column counts.
313 The EOL is defined as either '\r\n', or '\r', or '\n'.
314 A '\r' on its own is converted into a '\n' to correctly handle
315 raw input, typically due to 'copy-n-paste' of text files.
316 But a '\r\n' sequence is left ASIS for string handling
320 c=fgetc(token_info->stream); \
321 token_info->curr_column++; \
323 c=fgetc(token_info->stream); \
324 ungetc(c,token_info->stream); \
325 c = (c!='\n')?'\n':'\r'; \
328 token_info->curr_line++, token_info->curr_column=0; \
331 if ( (c>='\0' && c<'\a') || (c>'\r' && c<' ' && c!='\033') ) { \
332 token_info->status=TokenStatusBinary; \
336 /* macro to collect the token characters */
337 #define SaveChar(c) \
339 if ((size_t) offset >= (token_info->length-1)) { \
340 if ( token_info->length >= MagickPathExtent ) \
341 token_info->length += MagickPathExtent; \
343 token_info->length *= 4; \
344 token_info->token = (char *) \
345 ResizeMagickMemory(token_info->token, token_info->length); \
346 if ( token_info->token == (char *) NULL ) { \
347 token_info->status=TokenStatusMemoryFailed; \
351 token_info->token[offset++]=(char) (c); \
354 WandExport MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
366 /* EOF - no more tokens! */
367 if (token_info == (ScriptTokenInfo *) NULL)
369 if (token_info->status != TokenStatusOK)
371 token_info->token[0]='\0';
377 DisableMSCWarning(4127)
384 /* hash comment handling */
385 if ( state == IN_COMMENT ) {
390 /* comment lines start with '#' anywhere, or ':' or '@' at start of line */
391 if ( state == IN_WHITE )
393 ( token_info->curr_column==1 && (c == ':' || c == '@' ) ) )
395 /* whitespace token separator character */
396 if (strchr(" \n\r\t",c) != (char *) NULL) {
399 token_info->token[offset]='\0';
407 /* quote character */
408 if ( c=='\'' || c =='"' ) {
411 token_info->token_line=token_info->curr_line;
412 token_info->token_column=token_info->curr_column;
429 /* escape char (preserve in quotes - unless escaping the same quote) */
432 if ( state==IN_QUOTE && quote == '\'' ) {
440 state=IN_WHITE; /* end comment */
443 break; /* in double quotes only */
446 continue; /* line continuation - remove line feed */
450 token_info->token_line=token_info->curr_line;
451 token_info->token_column=token_info->curr_column;
455 if (c != quote && c != '\\')
462 /* ordinary character */
465 token_info->token_line=token_info->curr_line;
466 token_info->token_column=token_info->curr_column;
476 /* input stream has EOF or produced a fatal error */
477 token_info->token[offset]='\0';
478 if ( token_info->status != TokenStatusOK )
479 return(MagickFalse); /* fatal condition - no valid token */
480 token_info->status = TokenStatusEOF;
481 if ( state == IN_QUOTE)
482 token_info->status = TokenStatusBadQuotes;
483 if ( state == IN_TOKEN)
484 return(MagickTrue); /* token with EOF at end - no problem */
485 return(MagickFalse); /* in white space or in quotes - invalid token */