github.com/ezbuy/gauge@v0.9.4-0.20171013092048-7ac5bd3931cd/parser/stepParser.go (about)

     1  // Copyright 2015 ThoughtWorks, Inc.
     2  
     3  // This file is part of Gauge.
     4  
     5  // Gauge is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  
    10  // Gauge is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU General Public License for more details.
    14  
    15  // You should have received a copy of the GNU General Public License
    16  // along with Gauge.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package parser
    19  
    20  import (
    21  	"bytes"
    22  	"fmt"
    23  	"strconv"
    24  	"strings"
    25  )
    26  
    27  const (
    28  	inDefault      = 1 << iota
    29  	inQuotes       = 1 << iota
    30  	inEscape       = 1 << iota
    31  	inDynamicParam = 1 << iota
    32  	inSpecialParam = 1 << iota
    33  )
    34  const (
    35  	quotes                 = '"'
    36  	escape                 = '\\'
    37  	dynamicParamStart      = '<'
    38  	dynamicParamEnd        = '>'
    39  	specialParamIdentifier = ':'
    40  )
    41  
    42  var allEscapeChars = map[string]string{`\t`: "\t", `\n`: "\n", `\r`: "\r"}
    43  
    44  type acceptFn func(rune, int) (int, bool)
    45  
    46  func acceptor(start rune, end rune, onEachChar func(rune, int) int, after func(state int), inState int) acceptFn {
    47  
    48  	return func(element rune, currentState int) (int, bool) {
    49  		currentState = onEachChar(element, currentState)
    50  		if element == start {
    51  			if currentState == inDefault {
    52  				return inState, true
    53  			}
    54  		}
    55  		if element == end {
    56  			if currentState&inState != 0 {
    57  				after(currentState)
    58  				return inDefault, true
    59  			}
    60  		}
    61  		return currentState, false
    62  	}
    63  }
    64  
    65  func simpleAcceptor(start rune, end rune, after func(int), inState int) acceptFn {
    66  	onEach := func(currentChar rune, state int) int {
    67  		return state
    68  	}
    69  	return acceptor(start, end, onEach, after, inState)
    70  }
    71  
    72  func processStep(parser *SpecParser, token *Token) ([]error, bool) {
    73  	if len(token.Value) == 0 {
    74  		return []error{fmt.Errorf("Step should not be blank")}, true
    75  	}
    76  
    77  	stepValue, args, err := processStepText(token.Value)
    78  	if err != nil {
    79  		return []error{err}, true
    80  	}
    81  
    82  	token.Value = stepValue
    83  	token.Args = args
    84  	parser.clearState()
    85  	return []error{}, false
    86  }
    87  
    88  func processStepText(text string) (string, []string, error) {
    89  	reservedChars := map[rune]struct{}{'{': {}, '}': {}}
    90  	var stepValue, argText bytes.Buffer
    91  
    92  	var args []string
    93  
    94  	curBuffer := func(state int) *bytes.Buffer {
    95  		if isInAnyState(state, inQuotes, inDynamicParam) {
    96  			return &argText
    97  		}
    98  		return &stepValue
    99  	}
   100  
   101  	currentState := inDefault
   102  	lastState := -1
   103  
   104  	acceptStaticParam := simpleAcceptor(rune(quotes), rune(quotes), func(int) {
   105  		stepValue.WriteString("{static}")
   106  		args = append(args, argText.String())
   107  		argText.Reset()
   108  	}, inQuotes)
   109  
   110  	acceptSpecialDynamicParam := acceptor(rune(dynamicParamStart), rune(dynamicParamEnd), func(currentChar rune, state int) int {
   111  		if currentChar == specialParamIdentifier && state == inDynamicParam {
   112  			return state | inSpecialParam
   113  		}
   114  		return state
   115  	}, func(currentState int) {
   116  		if isInState(currentState, inSpecialParam) {
   117  			stepValue.WriteString("{special}")
   118  		} else {
   119  			stepValue.WriteString("{dynamic}")
   120  		}
   121  		args = append(args, argText.String())
   122  		argText.Reset()
   123  	}, inDynamicParam)
   124  
   125  	var inParamBoundary bool
   126  	for _, element := range text {
   127  		if currentState == inEscape {
   128  			currentState = lastState
   129  			if _, isReservedChar := reservedChars[element]; currentState == inDefault && !isReservedChar {
   130  				curBuffer(currentState).WriteRune(escape)
   131  			} else {
   132  				element = getEscapedRuneIfValid(element)
   133  			}
   134  		} else if element == escape {
   135  			lastState = currentState
   136  			currentState = inEscape
   137  			continue
   138  		} else if currentState, inParamBoundary = acceptSpecialDynamicParam(element, currentState); inParamBoundary {
   139  			continue
   140  		} else if currentState, inParamBoundary = acceptStaticParam(element, currentState); inParamBoundary {
   141  			continue
   142  		} else if _, isReservedChar := reservedChars[element]; currentState == inDefault && isReservedChar {
   143  			return "", nil, fmt.Errorf("'%c' is a reserved character and should be escaped", element)
   144  		}
   145  
   146  		curBuffer(currentState).WriteRune(element)
   147  	}
   148  
   149  	// If it is a valid step, the state should be default when the control reaches here
   150  	if currentState == inQuotes {
   151  		return "", nil, fmt.Errorf("String not terminated")
   152  	} else if isInState(currentState, inDynamicParam) {
   153  		return "", nil, fmt.Errorf("Dynamic parameter not terminated")
   154  	}
   155  
   156  	return strings.TrimSpace(stepValue.String()), args, nil
   157  
   158  }
   159  
   160  func getEscapedRuneIfValid(element rune) rune {
   161  	allEscapeChars := map[string]rune{"t": '\t', "n": '\n'}
   162  	elementToStr, err := strconv.Unquote(strconv.QuoteRune(element))
   163  	if err != nil {
   164  		return element
   165  	}
   166  	for key, val := range allEscapeChars {
   167  		if key == elementToStr {
   168  			return val
   169  		}
   170  	}
   171  	return element
   172  }