1 | /* |
---|
2 | * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc. |
---|
3 | * |
---|
4 | * This file is part of Jam - see jam.c for Copyright information. |
---|
5 | */ |
---|
6 | |
---|
7 | # include "jam.h" |
---|
8 | # include "lists.h" |
---|
9 | # include "variable.h" |
---|
10 | # include "expand.h" |
---|
11 | # include "pathsys.h" |
---|
12 | # include "newstr.h" |
---|
13 | # include <assert.h> |
---|
14 | |
---|
15 | # ifdef OS_CYGWIN |
---|
16 | # include <sys/cygwin.h> |
---|
17 | # include <windows.h> |
---|
18 | # include <stdlib.h> |
---|
19 | # endif |
---|
20 | |
---|
21 | /* |
---|
22 | * expand.c - expand a buffer, given variable values |
---|
23 | * |
---|
24 | * External routines: |
---|
25 | * |
---|
26 | * var_expand() - variable-expand input string into list of strings |
---|
27 | * |
---|
28 | * Internal routines: |
---|
29 | * |
---|
30 | * var_edit_parse() - parse : modifiers into PATHNAME structure |
---|
31 | * var_edit_file() - copy input target name to output, modifying filename |
---|
32 | * var_edit_shift() - do upshift/downshift mods |
---|
33 | * |
---|
34 | * 01/25/94 (seiwald) - $(X)$(UNDEF) was expanding like plain $(X) |
---|
35 | * 04/13/94 (seiwald) - added shorthand L0 for null list pointer |
---|
36 | * 01/11/01 (seiwald) - added support for :E=emptyvalue, :J=joinval |
---|
37 | */ |
---|
38 | |
---|
39 | typedef struct { |
---|
40 | PATHNAME f; /* :GDBSMR -- pieces */ |
---|
41 | char parent; /* :P -- go to parent directory */ |
---|
42 | char filemods; /* one of the above applied */ |
---|
43 | char downshift; /* :L -- downshift result */ |
---|
44 | char upshift; /* :U -- upshift result */ |
---|
45 | char to_slashes; /* :T -- convert "\" to "/" */ |
---|
46 | char to_windows; /* :W -- convert cygwin to native paths */ |
---|
47 | PATHPART empty; /* :E -- default for empties */ |
---|
48 | PATHPART join; /* :J -- join list with char */ |
---|
49 | } VAR_EDITS ; |
---|
50 | |
---|
51 | static void var_edit_parse( char *mods, VAR_EDITS *edits ); |
---|
52 | static void var_edit_file( char *in, string *out, VAR_EDITS *edits ); |
---|
53 | static void var_edit_shift( string *out, VAR_EDITS *edits ); |
---|
54 | |
---|
55 | # define MAGIC_COLON '\001' |
---|
56 | # define MAGIC_LEFT '\002' |
---|
57 | # define MAGIC_RIGHT '\003' |
---|
58 | |
---|
59 | /* |
---|
60 | * var_expand() - variable-expand input string into list of strings |
---|
61 | * |
---|
62 | * Would just copy input to output, performing variable expansion, |
---|
63 | * except that since variables can contain multiple values the result |
---|
64 | * of variable expansion may contain multiple values (a list). Properly |
---|
65 | * performs "product" operations that occur in "$(var1)xxx$(var2)" or |
---|
66 | * even "$($(var2))". |
---|
67 | * |
---|
68 | * Returns a newly created list. |
---|
69 | */ |
---|
70 | |
---|
71 | LIST * |
---|
72 | var_expand( |
---|
73 | LIST *l, |
---|
74 | char *in, |
---|
75 | char *end, |
---|
76 | LOL *lol, |
---|
77 | int cancopyin ) |
---|
78 | { |
---|
79 | char out_buf[ MAXSYM ]; |
---|
80 | string buf[1]; |
---|
81 | string out1[1]; /* Temporary buffer */ |
---|
82 | size_t prefix_length; |
---|
83 | char *out; |
---|
84 | char *inp = in; |
---|
85 | char *ov; /* for temp copy of variable in outbuf */ |
---|
86 | int depth; |
---|
87 | |
---|
88 | if( DEBUG_VAREXP ) |
---|
89 | printf( "expand '%.*s'\n", end - in, in ); |
---|
90 | |
---|
91 | /* This gets alot of cases: $(<) and $(>) */ |
---|
92 | |
---|
93 | if( in[0] == '$' && in[1] == '(' && in[3] == ')' && !in[4] ) |
---|
94 | { |
---|
95 | switch( in[2] ) |
---|
96 | { |
---|
97 | case '1': |
---|
98 | case '<': |
---|
99 | return list_copy( l, lol_get( lol, 0 ) ); |
---|
100 | |
---|
101 | case '2': |
---|
102 | case '>': |
---|
103 | return list_copy( l, lol_get( lol, 1 ) ); |
---|
104 | } |
---|
105 | } |
---|
106 | |
---|
107 | /* Just try simple copy of in to out. */ |
---|
108 | |
---|
109 | while( in < end ) |
---|
110 | if( *in++ == '$' && *in == '(' ) |
---|
111 | goto expand; |
---|
112 | |
---|
113 | /* No variables expanded - just add copy of input string to list. */ |
---|
114 | |
---|
115 | /* Cancopyin is an optimization: if the input was already a list */ |
---|
116 | /* item, we can use the copystr() to put it on the new list. */ |
---|
117 | /* Otherwise, we use the slower newstr(). */ |
---|
118 | |
---|
119 | if( cancopyin ) |
---|
120 | { |
---|
121 | return list_new( l, copystr( inp ) ); |
---|
122 | } |
---|
123 | else |
---|
124 | { |
---|
125 | LIST* r; |
---|
126 | string_new( buf ); |
---|
127 | string_append_range( buf, inp, end ); |
---|
128 | |
---|
129 | r = list_new( l, newstr( buf->value) ); |
---|
130 | string_free( buf ); |
---|
131 | return r; |
---|
132 | } |
---|
133 | |
---|
134 | expand: |
---|
135 | string_new( buf ); |
---|
136 | string_append_range( buf, inp, in - 1); /* copy the part before '$'. */ |
---|
137 | /* |
---|
138 | * Input so far (ignore blanks): |
---|
139 | * |
---|
140 | * stuff-in-outbuf $(variable) remainder |
---|
141 | * ^ ^ |
---|
142 | * in end |
---|
143 | * Output so far: |
---|
144 | * |
---|
145 | * stuff-in-outbuf $ |
---|
146 | * ^ ^ |
---|
147 | * out_buf out |
---|
148 | * |
---|
149 | * |
---|
150 | * We just copied the $ of $(...), so back up one on the output. |
---|
151 | * We now find the matching close paren, copying the variable and |
---|
152 | * modifiers between the $( and ) temporarily into out_buf, so that |
---|
153 | * we can replace :'s with MAGIC_COLON. This is necessary to avoid |
---|
154 | * being confused by modifier values that are variables containing |
---|
155 | * :'s. Ugly. |
---|
156 | */ |
---|
157 | |
---|
158 | depth = 1; |
---|
159 | inp = ++in; /* skip over the '(' */ |
---|
160 | |
---|
161 | while( in < end && depth ) |
---|
162 | { |
---|
163 | switch( *in++ ) |
---|
164 | { |
---|
165 | case '(': depth++; break; |
---|
166 | case ')': depth--; break; |
---|
167 | } |
---|
168 | } |
---|
169 | |
---|
170 | /* |
---|
171 | * Input so far (ignore blanks): |
---|
172 | * |
---|
173 | * stuff-in-outbuf $(variable) remainder |
---|
174 | * ^ ^ ^ |
---|
175 | * inp in end |
---|
176 | */ |
---|
177 | prefix_length = buf->size; |
---|
178 | string_append_range( buf, inp, in - 1 ); |
---|
179 | |
---|
180 | out = buf->value + prefix_length; |
---|
181 | for ( ov = out; ov < buf->value + buf->size; ++ov ) |
---|
182 | { |
---|
183 | switch( *ov ) |
---|
184 | { |
---|
185 | case ':': *ov = MAGIC_COLON; break; |
---|
186 | case '[': *ov = MAGIC_LEFT; break; |
---|
187 | case ']': *ov = MAGIC_RIGHT; break; |
---|
188 | } |
---|
189 | } |
---|
190 | |
---|
191 | /* |
---|
192 | * Input so far (ignore blanks): |
---|
193 | * |
---|
194 | * stuff-in-outbuf $(variable) remainder |
---|
195 | * ^ ^ |
---|
196 | * in end |
---|
197 | * Output so far: |
---|
198 | * |
---|
199 | * stuff-in-outbuf variable |
---|
200 | * ^ ^ ^ |
---|
201 | * out_buf out ov |
---|
202 | * |
---|
203 | * Later we will overwrite 'variable' in out_buf, but we'll be |
---|
204 | * done with it by then. 'variable' may be a multi-element list, |
---|
205 | * so may each value for '$(variable element)', and so may 'remainder'. |
---|
206 | * Thus we produce a product of three lists. |
---|
207 | */ |
---|
208 | |
---|
209 | { |
---|
210 | LIST *variables = 0; |
---|
211 | LIST *remainder = 0; |
---|
212 | LIST *vars; |
---|
213 | |
---|
214 | /* Recursively expand variable name & rest of input */ |
---|
215 | |
---|
216 | if( out < ov ) |
---|
217 | variables = var_expand( L0, out, ov, lol, 0 ); |
---|
218 | if( in < end ) |
---|
219 | remainder = var_expand( L0, in, end, lol, 0 ); |
---|
220 | |
---|
221 | /* Now produce the result chain */ |
---|
222 | |
---|
223 | /* For each variable name */ |
---|
224 | |
---|
225 | for( vars = variables; vars; vars = list_next( vars ) ) |
---|
226 | { |
---|
227 | LIST *value, *evalue = 0; |
---|
228 | char *colon; |
---|
229 | char *bracket; |
---|
230 | string variable[1]; |
---|
231 | char *varname; |
---|
232 | int sub1 = 0, sub2 = -1; |
---|
233 | VAR_EDITS edits; |
---|
234 | |
---|
235 | /* Look for a : modifier in the variable name */ |
---|
236 | /* Must copy into varname so we can modify it */ |
---|
237 | |
---|
238 | string_copy( variable, vars->string ); |
---|
239 | varname = variable->value; |
---|
240 | |
---|
241 | if( colon = strchr( varname, MAGIC_COLON ) ) |
---|
242 | { |
---|
243 | string_truncate( variable, colon - varname ); |
---|
244 | var_edit_parse( colon + 1, &edits ); |
---|
245 | } |
---|
246 | |
---|
247 | /* Look for [x-y] subscripting */ |
---|
248 | /* sub1 and sub2 are x and y. */ |
---|
249 | |
---|
250 | if ( bracket = strchr( varname, MAGIC_LEFT ) ) |
---|
251 | { |
---|
252 | /* |
---|
253 | ** Make all syntax errors in [] subscripting |
---|
254 | ** result in the same behavior: silenty return an empty |
---|
255 | ** expansion (by setting sub2 = 0). Brute force parsing; |
---|
256 | ** May get moved into yacc someday. |
---|
257 | */ |
---|
258 | |
---|
259 | char *s = bracket + 1; |
---|
260 | |
---|
261 | string_truncate( variable, bracket - varname ); |
---|
262 | |
---|
263 | do /* so we can use "break" */ |
---|
264 | { |
---|
265 | /* Allow negative indexes. */ |
---|
266 | if (! isdigit( *s ) && ! ( *s == '-') ) |
---|
267 | { |
---|
268 | sub2 = 0; |
---|
269 | break; |
---|
270 | } |
---|
271 | sub1 = atoi(s); |
---|
272 | |
---|
273 | /* Skip over the first symbol, which is either a digit or dash. */ |
---|
274 | s++; |
---|
275 | while ( isdigit( *s ) ) s++; |
---|
276 | |
---|
277 | if ( *s == MAGIC_RIGHT ) |
---|
278 | { |
---|
279 | sub2 = sub1; |
---|
280 | break; |
---|
281 | } |
---|
282 | |
---|
283 | if ( *s != '-') |
---|
284 | { |
---|
285 | sub2 = 0; |
---|
286 | break; |
---|
287 | } |
---|
288 | |
---|
289 | s++; |
---|
290 | |
---|
291 | if ( *s == MAGIC_RIGHT ) |
---|
292 | { |
---|
293 | sub2 = -1; |
---|
294 | break; |
---|
295 | } |
---|
296 | |
---|
297 | if (! isdigit( *s ) && ! ( *s == '-') ) |
---|
298 | { |
---|
299 | sub2 = 0; |
---|
300 | break; |
---|
301 | } |
---|
302 | |
---|
303 | /* First, compute the index of the last element. */ |
---|
304 | sub2 = atoi(s); |
---|
305 | s++; |
---|
306 | while ( isdigit( *s ) ) s++; |
---|
307 | |
---|
308 | if ( *s != MAGIC_RIGHT) |
---|
309 | sub2 = 0; |
---|
310 | |
---|
311 | } while (0); |
---|
312 | |
---|
313 | /* |
---|
314 | ** Anything but the end of the string, or the colon |
---|
315 | ** introducing a modifier is a syntax error. |
---|
316 | */ |
---|
317 | |
---|
318 | s++; |
---|
319 | if (*s && *s != MAGIC_COLON) |
---|
320 | sub2 = 0; |
---|
321 | |
---|
322 | *bracket = '\0'; |
---|
323 | } |
---|
324 | |
---|
325 | /* Get variable value, specially handling $(<), $(>), $(n) */ |
---|
326 | |
---|
327 | if( varname[0] == '<' && !varname[1] ) |
---|
328 | value = lol_get( lol, 0 ); |
---|
329 | else if( varname[0] == '>' && !varname[1] ) |
---|
330 | value = lol_get( lol, 1 ); |
---|
331 | else if( varname[0] >= '1' && varname[0] <= '9' && !varname[1] ) |
---|
332 | value = lol_get( lol, varname[0] - '1' ); |
---|
333 | else |
---|
334 | value = var_get( varname ); |
---|
335 | |
---|
336 | /* Handle negitive indexes: part two. */ |
---|
337 | { |
---|
338 | int length = list_length( value ); |
---|
339 | |
---|
340 | if (sub1 < 0) |
---|
341 | sub1 = length + sub1; |
---|
342 | else |
---|
343 | sub1 -= 1; |
---|
344 | |
---|
345 | if (sub2 < 0) |
---|
346 | sub2 = length + 1 + sub2 - sub1; |
---|
347 | else |
---|
348 | sub2 -= sub1; |
---|
349 | /* |
---|
350 | ** The "sub2 < 0" test handles the semantic error |
---|
351 | ** of sub2 < sub1. |
---|
352 | */ |
---|
353 | if ( sub2 < 0 ) |
---|
354 | sub2 = 0; |
---|
355 | } |
---|
356 | |
---|
357 | |
---|
358 | |
---|
359 | /* The fast path: $(x) - just copy the variable value. */ |
---|
360 | /* This is only an optimization */ |
---|
361 | |
---|
362 | if( out == out_buf && !bracket && !colon && in == end ) |
---|
363 | { |
---|
364 | string_free( variable ); |
---|
365 | l = list_copy( l, value ); |
---|
366 | continue; |
---|
367 | } |
---|
368 | |
---|
369 | /* Handle start subscript */ |
---|
370 | |
---|
371 | while( sub1 > 0 && value ) |
---|
372 | --sub1, value = list_next( value ); |
---|
373 | |
---|
374 | /* Empty w/ :E=default? */ |
---|
375 | |
---|
376 | if( !value && colon && edits.empty.ptr ) |
---|
377 | evalue = value = list_new( L0, newstr( edits.empty.ptr ) ); |
---|
378 | |
---|
379 | /* For each variable value */ |
---|
380 | |
---|
381 | string_new( out1 ); |
---|
382 | for( ; value; value = list_next( value ) ) |
---|
383 | { |
---|
384 | LIST *rem; |
---|
385 | size_t postfix_start; |
---|
386 | |
---|
387 | /* Handle end subscript (length actually) */ |
---|
388 | |
---|
389 | if( sub2 >= 0 && --sub2 < 0 ) |
---|
390 | break; |
---|
391 | |
---|
392 | string_truncate( buf, prefix_length ); |
---|
393 | |
---|
394 | /* Apply : mods, if present */ |
---|
395 | |
---|
396 | if( colon && edits.filemods ) |
---|
397 | var_edit_file( value->string, out1, &edits ); |
---|
398 | else |
---|
399 | string_append( out1, value->string ); |
---|
400 | |
---|
401 | if( colon && ( edits.upshift || edits.downshift || edits.to_slashes || edits.to_windows ) ) |
---|
402 | var_edit_shift( out1, &edits ); |
---|
403 | |
---|
404 | /* Handle :J=joinval */ |
---|
405 | /* If we have more values for this var, just */ |
---|
406 | /* keep appending them (with the join value) */ |
---|
407 | /* rather than creating separate LIST elements. */ |
---|
408 | |
---|
409 | if( colon && edits.join.ptr && |
---|
410 | ( list_next( value ) || list_next( vars ) ) ) |
---|
411 | { |
---|
412 | string_append( out1, edits.join.ptr ); |
---|
413 | continue; |
---|
414 | } |
---|
415 | |
---|
416 | string_append( buf, out1->value ); |
---|
417 | string_free( out1 ); |
---|
418 | string_new( out1 ); |
---|
419 | |
---|
420 | /* If no remainder, append result to output chain. */ |
---|
421 | |
---|
422 | if( in == end ) |
---|
423 | { |
---|
424 | l = list_new( l, newstr( buf->value ) ); |
---|
425 | continue; |
---|
426 | } |
---|
427 | |
---|
428 | /* For each remainder, append the complete string */ |
---|
429 | /* to the output chain. */ |
---|
430 | /* Remember the end of the variable expansion so */ |
---|
431 | /* we can just tack on each instance of 'remainder' */ |
---|
432 | |
---|
433 | postfix_start = buf->size; |
---|
434 | |
---|
435 | for( rem = remainder; rem; rem = list_next( rem ) ) |
---|
436 | { |
---|
437 | string_truncate( buf, postfix_start ); |
---|
438 | string_append( buf, rem->string ); |
---|
439 | l = list_new( l, newstr( buf->value ) ); |
---|
440 | } |
---|
441 | } |
---|
442 | string_free( out1 ); |
---|
443 | |
---|
444 | /* Toss used empty */ |
---|
445 | |
---|
446 | if( evalue ) |
---|
447 | list_free( evalue ); |
---|
448 | |
---|
449 | string_free( variable ); |
---|
450 | } |
---|
451 | |
---|
452 | /* variables & remainder were gifts from var_expand */ |
---|
453 | /* and must be freed */ |
---|
454 | |
---|
455 | if( variables ) |
---|
456 | list_free( variables ); |
---|
457 | if( remainder) |
---|
458 | list_free( remainder ); |
---|
459 | |
---|
460 | if( DEBUG_VAREXP ) |
---|
461 | { |
---|
462 | printf( "expanded to " ); |
---|
463 | list_print( l ); |
---|
464 | printf( "\n" ); |
---|
465 | } |
---|
466 | |
---|
467 | string_free( buf ); |
---|
468 | return l; |
---|
469 | } |
---|
470 | } |
---|
471 | |
---|
472 | /* |
---|
473 | * var_edit_parse() - parse : modifiers into PATHNAME structure |
---|
474 | * |
---|
475 | * The : modifiers in a $(varname:modifier) currently support replacing |
---|
476 | * or omitting elements of a filename, and so they are parsed into a |
---|
477 | * PATHNAME structure (which contains pointers into the original string). |
---|
478 | * |
---|
479 | * Modifiers of the form "X=value" replace the component X with |
---|
480 | * the given value. Modifiers without the "=value" cause everything |
---|
481 | * but the component X to be omitted. X is one of: |
---|
482 | * |
---|
483 | * G <grist> |
---|
484 | * D directory name |
---|
485 | * B base name |
---|
486 | * S .suffix |
---|
487 | * M (member) |
---|
488 | * R root directory - prepended to whole path |
---|
489 | * |
---|
490 | * This routine sets: |
---|
491 | * |
---|
492 | * f->f_xxx.ptr = 0 |
---|
493 | * f->f_xxx.len = 0 |
---|
494 | * -> leave the original component xxx |
---|
495 | * |
---|
496 | * f->f_xxx.ptr = string |
---|
497 | * f->f_xxx.len = strlen( string ) |
---|
498 | * -> replace component xxx with string |
---|
499 | * |
---|
500 | * f->f_xxx.ptr = "" |
---|
501 | * f->f_xxx.len = 0 |
---|
502 | * -> omit component xxx |
---|
503 | * |
---|
504 | * var_edit_file() below and path_build() obligingly follow this convention. |
---|
505 | */ |
---|
506 | |
---|
507 | static void |
---|
508 | var_edit_parse( |
---|
509 | char *mods, |
---|
510 | VAR_EDITS *edits ) |
---|
511 | { |
---|
512 | int havezeroed = 0; |
---|
513 | memset( (char *)edits, 0, sizeof( *edits ) ); |
---|
514 | |
---|
515 | while( *mods ) |
---|
516 | { |
---|
517 | char *p; |
---|
518 | PATHPART *fp; |
---|
519 | |
---|
520 | switch( *mods++ ) |
---|
521 | { |
---|
522 | case 'L': edits->downshift = 1; continue; |
---|
523 | case 'U': edits->upshift = 1; continue; |
---|
524 | case 'P': edits->parent = edits->filemods = 1; continue; |
---|
525 | case 'E': fp = &edits->empty; goto strval; |
---|
526 | case 'J': fp = &edits->join; goto strval; |
---|
527 | case 'G': fp = &edits->f.f_grist; goto fileval; |
---|
528 | case 'R': fp = &edits->f.f_root; goto fileval; |
---|
529 | case 'D': fp = &edits->f.f_dir; goto fileval; |
---|
530 | case 'B': fp = &edits->f.f_base; goto fileval; |
---|
531 | case 'S': fp = &edits->f.f_suffix; goto fileval; |
---|
532 | case 'M': fp = &edits->f.f_member; goto fileval; |
---|
533 | case 'T': edits->to_slashes = 1; continue; |
---|
534 | case 'W': edits->to_windows = 1; continue; |
---|
535 | |
---|
536 | default: return; /* should complain, but so what... */ |
---|
537 | } |
---|
538 | |
---|
539 | fileval: |
---|
540 | |
---|
541 | /* Handle :CHARS, where each char (without a following =) */ |
---|
542 | /* selects a particular file path element. On the first such */ |
---|
543 | /* char, we deselect all others (by setting ptr = "", len = 0) */ |
---|
544 | /* and for each char we select that element (by setting ptr = 0) */ |
---|
545 | |
---|
546 | edits->filemods = 1; |
---|
547 | |
---|
548 | if( *mods != '=' ) |
---|
549 | { |
---|
550 | int i; |
---|
551 | |
---|
552 | if( !havezeroed++ ) |
---|
553 | for( i = 0; i < 6; i++ ) |
---|
554 | { |
---|
555 | edits->f.part[ i ].len = 0; |
---|
556 | edits->f.part[ i ].ptr = ""; |
---|
557 | } |
---|
558 | |
---|
559 | fp->ptr = 0; |
---|
560 | continue; |
---|
561 | } |
---|
562 | |
---|
563 | strval: |
---|
564 | |
---|
565 | /* Handle :X=value, or :X */ |
---|
566 | |
---|
567 | if( *mods != '=' ) |
---|
568 | { |
---|
569 | fp->ptr = ""; |
---|
570 | fp->len = 0; |
---|
571 | } |
---|
572 | else if( p = strchr( mods, MAGIC_COLON ) ) |
---|
573 | { |
---|
574 | *p = 0; |
---|
575 | fp->ptr = ++mods; |
---|
576 | fp->len = p - mods; |
---|
577 | mods = p + 1; |
---|
578 | } |
---|
579 | else |
---|
580 | { |
---|
581 | fp->ptr = ++mods; |
---|
582 | fp->len = strlen( mods ); |
---|
583 | mods += fp->len; |
---|
584 | } |
---|
585 | } |
---|
586 | } |
---|
587 | |
---|
588 | /* |
---|
589 | * var_edit_file() - copy input target name to output, modifying filename |
---|
590 | */ |
---|
591 | |
---|
592 | static void |
---|
593 | var_edit_file( |
---|
594 | char *in, |
---|
595 | string *out, |
---|
596 | VAR_EDITS *edits ) |
---|
597 | { |
---|
598 | PATHNAME pathname; |
---|
599 | |
---|
600 | /* Parse apart original filename, putting parts into "pathname" */ |
---|
601 | |
---|
602 | path_parse( in, &pathname ); |
---|
603 | |
---|
604 | /* Replace any pathname with edits->f */ |
---|
605 | |
---|
606 | if( edits->f.f_grist.ptr ) |
---|
607 | pathname.f_grist = edits->f.f_grist; |
---|
608 | |
---|
609 | if( edits->f.f_root.ptr ) |
---|
610 | pathname.f_root = edits->f.f_root; |
---|
611 | |
---|
612 | if( edits->f.f_dir.ptr ) |
---|
613 | pathname.f_dir = edits->f.f_dir; |
---|
614 | |
---|
615 | if( edits->f.f_base.ptr ) |
---|
616 | pathname.f_base = edits->f.f_base; |
---|
617 | |
---|
618 | if( edits->f.f_suffix.ptr ) |
---|
619 | pathname.f_suffix = edits->f.f_suffix; |
---|
620 | |
---|
621 | if( edits->f.f_member.ptr ) |
---|
622 | pathname.f_member = edits->f.f_member; |
---|
623 | |
---|
624 | /* If requested, modify pathname to point to parent */ |
---|
625 | |
---|
626 | if( edits->parent ) |
---|
627 | path_parent( &pathname ); |
---|
628 | |
---|
629 | /* Put filename back together */ |
---|
630 | |
---|
631 | path_build( &pathname, out, 0 ); |
---|
632 | } |
---|
633 | |
---|
634 | /* |
---|
635 | * var_edit_shift() - do upshift/downshift mods |
---|
636 | */ |
---|
637 | |
---|
638 | static void |
---|
639 | var_edit_shift( |
---|
640 | string *out, |
---|
641 | VAR_EDITS *edits ) |
---|
642 | { |
---|
643 | /* Handle upshifting, downshifting and slash translation now */ |
---|
644 | |
---|
645 | char *p; |
---|
646 | for ( p = out->value; *p; ++p) |
---|
647 | { |
---|
648 | if (edits->upshift) |
---|
649 | { |
---|
650 | *p = toupper( *p ); |
---|
651 | } |
---|
652 | else if ( edits->downshift ) |
---|
653 | { |
---|
654 | *p = tolower( *p ); |
---|
655 | } |
---|
656 | if ( edits->to_slashes ) |
---|
657 | { |
---|
658 | if ( *p == '\\') |
---|
659 | *p = '/'; |
---|
660 | } |
---|
661 | # ifdef OS_CYGWIN |
---|
662 | if ( edits->to_windows ) |
---|
663 | { |
---|
664 | char result[MAX_PATH + 1]; |
---|
665 | cygwin_conv_to_win32_path(out->value, result); |
---|
666 | assert(strlen(result) <= MAX_PATH); |
---|
667 | string_free( out ); |
---|
668 | string_copy( out, result ); |
---|
669 | } |
---|
670 | # endif |
---|
671 | } |
---|
672 | out->size = p - out->value; |
---|
673 | } |
---|
674 | |
---|
675 | #ifndef NDEBUG |
---|
676 | void var_expand_unit_test() |
---|
677 | { |
---|
678 | LOL lol[1]; |
---|
679 | LIST* l, *l2; |
---|
680 | LIST *expected = list_new( list_new( L0, newstr( "axb" ) ), newstr( "ayb" ) ); |
---|
681 | LIST *e2; |
---|
682 | char axyb[] = "a$(xy)b"; |
---|
683 | char azb[] = "a$($(z))b"; |
---|
684 | char path[] = "$(p:W)"; |
---|
685 | |
---|
686 | # ifdef OS_CYGWIN |
---|
687 | char cygpath[256]; |
---|
688 | cygwin_conv_to_posix_path("c:\\foo\\bar", cygpath); |
---|
689 | # else |
---|
690 | char cygpath[] = "/cygdrive/c/foo/bar"; |
---|
691 | # endif |
---|
692 | |
---|
693 | lol_init(lol); |
---|
694 | var_set("xy", list_new( list_new( L0, newstr( "x" ) ), newstr( "y" ) ), VAR_SET ); |
---|
695 | var_set("z", list_new( L0, newstr( "xy" ) ), VAR_SET ); |
---|
696 | var_set("p", list_new( L0, newstr( cygpath ) ), VAR_SET ); |
---|
697 | |
---|
698 | l = var_expand( 0, axyb, axyb + sizeof(axyb) - 1, lol, 0 ); |
---|
699 | for ( l2 = l, e2 = expected; l2 && e2; l2 = list_next(l2), e2 = list_next(e2) ) |
---|
700 | assert( !strcmp( e2->string, l2->string ) ); |
---|
701 | assert(l2 == 0 && e2 == 0); |
---|
702 | list_free(l); |
---|
703 | |
---|
704 | l = var_expand( 0, azb, azb + sizeof(azb) - 1, lol, 0 ); |
---|
705 | for ( l2 = l, e2 = expected; l2 && e2; l2 = list_next(l2), e2 = list_next(e2) ) |
---|
706 | assert( !strcmp( e2->string, l2->string ) ); |
---|
707 | assert(l2 == 0 && e2 == 0); |
---|
708 | list_free(l); |
---|
709 | |
---|
710 | l = var_expand( 0, path, path + sizeof(path) - 1, lol, 0 ); |
---|
711 | assert(l != 0); |
---|
712 | assert(list_next(l) == 0); |
---|
713 | # ifdef OS_CYGWIN |
---|
714 | assert( !strcmp( l->string, "c:\\foo\\bar" ) ); |
---|
715 | # else |
---|
716 | assert( !strcmp( l->string, cygpath ) ); |
---|
717 | # endif |
---|
718 | list_free(l); |
---|
719 | |
---|
720 | list_free(expected); |
---|
721 | |
---|
722 | lol_free(lol); |
---|
723 | } |
---|
724 | #endif |
---|
725 | |
---|
726 | /* |
---|
727 | Local Variables: |
---|
728 | tab-width: 8 |
---|
729 | End: |
---|
730 | */ |
---|