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