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 }