github.com/olljanat/moby@v1.13.1/builder/dockerfile/parser/utils.go (about)

     1  package parser
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"unicode"
     8  )
     9  
    10  // Dump dumps the AST defined by `node` as a list of sexps.
    11  // Returns a string suitable for printing.
    12  func (node *Node) Dump() string {
    13  	str := ""
    14  	str += node.Value
    15  
    16  	if len(node.Flags) > 0 {
    17  		str += fmt.Sprintf(" %q", node.Flags)
    18  	}
    19  
    20  	for _, n := range node.Children {
    21  		str += "(" + n.Dump() + ")\n"
    22  	}
    23  
    24  	if node.Next != nil {
    25  		for n := node.Next; n != nil; n = n.Next {
    26  			if len(n.Children) > 0 {
    27  				str += " " + n.Dump()
    28  			} else {
    29  				str += " " + strconv.Quote(n.Value)
    30  			}
    31  		}
    32  	}
    33  
    34  	return strings.TrimSpace(str)
    35  }
    36  
    37  // performs the dispatch based on the two primal strings, cmd and args. Please
    38  // look at the dispatch table in parser.go to see how these dispatchers work.
    39  func fullDispatch(cmd, args string, d *Directive) (*Node, map[string]bool, error) {
    40  	fn := dispatch[cmd]
    41  
    42  	// Ignore invalid Dockerfile instructions
    43  	if fn == nil {
    44  		fn = parseIgnore
    45  	}
    46  
    47  	sexp, attrs, err := fn(args, d)
    48  	if err != nil {
    49  		return nil, nil, err
    50  	}
    51  
    52  	return sexp, attrs, nil
    53  }
    54  
    55  // splitCommand takes a single line of text and parses out the cmd and args,
    56  // which are used for dispatching to more exact parsing functions.
    57  func splitCommand(line string) (string, []string, string, error) {
    58  	var args string
    59  	var flags []string
    60  
    61  	// Make sure we get the same results irrespective of leading/trailing spaces
    62  	cmdline := tokenWhitespace.Split(strings.TrimSpace(line), 2)
    63  	cmd := strings.ToLower(cmdline[0])
    64  
    65  	if len(cmdline) == 2 {
    66  		var err error
    67  		args, flags, err = extractBuilderFlags(cmdline[1])
    68  		if err != nil {
    69  			return "", nil, "", err
    70  		}
    71  	}
    72  
    73  	return cmd, flags, strings.TrimSpace(args), nil
    74  }
    75  
    76  // covers comments and empty lines. Lines should be trimmed before passing to
    77  // this function.
    78  func stripComments(line string) string {
    79  	// string is already trimmed at this point
    80  	if tokenComment.MatchString(line) {
    81  		return tokenComment.ReplaceAllString(line, "")
    82  	}
    83  
    84  	return line
    85  }
    86  
    87  func extractBuilderFlags(line string) (string, []string, error) {
    88  	// Parses the BuilderFlags and returns the remaining part of the line
    89  
    90  	const (
    91  		inSpaces = iota // looking for start of a word
    92  		inWord
    93  		inQuote
    94  	)
    95  
    96  	words := []string{}
    97  	phase := inSpaces
    98  	word := ""
    99  	quote := '\000'
   100  	blankOK := false
   101  	var ch rune
   102  
   103  	for pos := 0; pos <= len(line); pos++ {
   104  		if pos != len(line) {
   105  			ch = rune(line[pos])
   106  		}
   107  
   108  		if phase == inSpaces { // Looking for start of word
   109  			if pos == len(line) { // end of input
   110  				break
   111  			}
   112  			if unicode.IsSpace(ch) { // skip spaces
   113  				continue
   114  			}
   115  
   116  			// Only keep going if the next word starts with --
   117  			if ch != '-' || pos+1 == len(line) || rune(line[pos+1]) != '-' {
   118  				return line[pos:], words, nil
   119  			}
   120  
   121  			phase = inWord // found someting with "--", fall through
   122  		}
   123  		if (phase == inWord || phase == inQuote) && (pos == len(line)) {
   124  			if word != "--" && (blankOK || len(word) > 0) {
   125  				words = append(words, word)
   126  			}
   127  			break
   128  		}
   129  		if phase == inWord {
   130  			if unicode.IsSpace(ch) {
   131  				phase = inSpaces
   132  				if word == "--" {
   133  					return line[pos:], words, nil
   134  				}
   135  				if blankOK || len(word) > 0 {
   136  					words = append(words, word)
   137  				}
   138  				word = ""
   139  				blankOK = false
   140  				continue
   141  			}
   142  			if ch == '\'' || ch == '"' {
   143  				quote = ch
   144  				blankOK = true
   145  				phase = inQuote
   146  				continue
   147  			}
   148  			if ch == '\\' {
   149  				if pos+1 == len(line) {
   150  					continue // just skip \ at end
   151  				}
   152  				pos++
   153  				ch = rune(line[pos])
   154  			}
   155  			word += string(ch)
   156  			continue
   157  		}
   158  		if phase == inQuote {
   159  			if ch == quote {
   160  				phase = inWord
   161  				continue
   162  			}
   163  			if ch == '\\' {
   164  				if pos+1 == len(line) {
   165  					phase = inWord
   166  					continue // just skip \ at end
   167  				}
   168  				pos++
   169  				ch = rune(line[pos])
   170  			}
   171  			word += string(ch)
   172  		}
   173  	}
   174  
   175  	return "", words, nil
   176  }