@@ -214,14 +214,22 @@ func (v *Vars) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error
214214 v .state = varEmit
215215 return nDst , nSrc , transform .ErrShortDst
216216 case v .state == varError :
217- return nDst , nSrc , fmt .Errorf ("dockerfile: bad expansion of %q: %s" , v .varName .String (), v .varExpand .String ())
217+ return nDst , nSrc , fmt .Errorf ("dockerfile: bad expansion of %q: %s (%v)" ,
218+ v .varName .String (),
219+ v .varExpand .String (),
220+ v .expand ,
221+ )
218222 }
219223 nDst += n
220224 v .state = varConsume
221225 default :
222226 panic ("state botch" )
223227 }
224228 }
229+ if v .esc {
230+ // Ended in a "bare" escape character. Just pass it through.
231+ nDst += utf8 .EncodeRune (dst [nDst :], v .escchar )
232+ }
225233 if v .state == varBareword && atEOF {
226234 // Hit EOF, so variable name is complete.
227235 n , done := v .emit (dst [nDst :])
@@ -296,9 +304,22 @@ func (v *Vars) emit(dst []byte) (int, bool) {
296304 suffix := v .expand == varTrimSuffix || v .expand == varTrimSuffixGreedy
297305 re , err := convertPattern ([]byte (word ), greedy , suffix )
298306 if err != nil {
299- panic ("TODO(hank): TrimPrefix/TrimSuffix: " + err .Error ())
307+ v .state = varError
308+ return 0 , true
309+ }
310+ ms := re .FindStringSubmatch (val )
311+ switch len (ms ) {
312+ case 0 , 1 :
313+ // No match, do nothing.
314+ case 2 :
315+ if suffix {
316+ val = strings .TrimSuffix (val , ms [1 ])
317+ } else {
318+ val = strings .TrimPrefix (val , ms [1 ])
319+ }
320+ default :
321+ panic (fmt .Sprintf ("pattern compiler is acting up; got: %#v" , ms ))
300322 }
301- val = re .ReplaceAllLiteralString (val , "" )
302323 default :
303324 panic ("expand state botch" )
304325 }
@@ -311,16 +332,28 @@ func (v *Vars) emit(dst []byte) (int, bool) {
311332
312333// ConvertPattern transforms "pat" from (something like) the POSIX sh pattern
313334// language to a regular expression, then returns the compiled regexp.
335+ //
336+ // The resulting regexp reports the prefix/suffix to be removed as the first
337+ // submatch when executed.
338+ //
339+ // This conversion is tricky, because extra hoops are needed to work around the
340+ // leftmost-first behavior.
314341func convertPattern (pat []byte , greedy bool , suffix bool ) (_ * regexp.Regexp , err error ) {
315342 var rePat strings.Builder
316343 rePat .Grow (len (pat ) * 2 ) // 🤷
317-
318- if ! greedy {
319- rePat .WriteString (`(?U)` )
344+ // This is needed to "push" a suffix pattern to the correct place. Note that
345+ // the "greediness" is backwards: this is the input that's _not_ the
346+ // pattern.
347+ pad := `(?:.*)`
348+ if greedy {
349+ pad = `(?:.*?)`
320350 }
321- if ! suffix {
322- rePat .WriteByte ('^' )
351+
352+ rePat .WriteByte ('^' )
353+ if suffix {
354+ rePat .WriteString (pad )
323355 }
356+ rePat .WriteByte ('(' )
324357 off := 0
325358 r , sz := rune (0 ), 0
326359 for ; off < len (pat ); off += sz {
@@ -332,6 +365,9 @@ func convertPattern(pat []byte, greedy bool, suffix bool) (_ *regexp.Regexp, err
332365 switch r {
333366 case '*' : // Kleene star
334367 rePat .WriteString (`.*` )
368+ if ! suffix && ! greedy {
369+ rePat .WriteByte ('?' )
370+ }
335371 case '?' : // Single char
336372 rePat .WriteByte ('.' )
337373 case '\\' :
@@ -355,9 +391,11 @@ func convertPattern(pat []byte, greedy bool, suffix bool) (_ *regexp.Regexp, err
355391 rePat .WriteRune (r )
356392 }
357393 }
358- if suffix {
359- rePat .WriteByte ('$' )
394+ rePat .WriteByte (')' )
395+ if ! suffix {
396+ rePat .WriteString (pad )
360397 }
398+ rePat .WriteByte ('$' )
361399
362400 return regexp .Compile (rePat .String ())
363401}
@@ -430,26 +468,17 @@ const (
430468type varExpand uint8
431469
432470const (
433- // Expand to the named variable or the empty string.
434- varExpandSimple varExpand = iota
435- // Expand to the named variable or the provided word.
436- varExpandDefault
437- varExpandDefaultNull
438- // Set the named variable to the provided word if unset, then expand to the named variable.
439- varSetDefault
440- varSetDefaultNull
441- // Expand to the provided word or the empty string.
442- varExpandAlternate
443- varExpandAlternateNull
444- // Error if unset.
445- varErrIfUnset
446- varErrIfUnsetNull
447- // Expand by interpreting "word" as a pattern, then expanding "parameter" and removing the smallest suffix.
448- varTrimSuffix
449- // Expand by interpreting "word" as a pattern, then expanding "parameter" and removing the largest suffix.
450- varTrimSuffixGreedy
451- // Expand by interpreting "word" as a pattern, then expanding "parameter" and removing the smallest prefix.
452- varTrimPrefix
453- // Expand by interpreting "word" as a pattern, then expanding "parameter" and removing the largest prefix.
454- varTrimPrefixGreedy
471+ varExpandSimple varExpand = iota // simple expansion
472+ varExpandDefault // default expansion
473+ varExpandDefaultNull // default+null expansion
474+ varSetDefault // set default
475+ varSetDefaultNull // set default, incl. null
476+ varExpandAlternate // alternate expansion
477+ varExpandAlternateNull // alternate expanxion, incl. null
478+ varErrIfUnset // error if unset
479+ varErrIfUnsetNull // error if unset or null
480+ varTrimSuffix // trim suffix
481+ varTrimSuffixGreedy // greedy trim suffix
482+ varTrimPrefix // trim prefix
483+ varTrimPrefixGreedy // greedy trim prefix
455484)
0 commit comments