Skip to content

Commit

Permalink
address #346
Browse files Browse the repository at this point in the history
  • Loading branch information
charlierudolph committed Apr 4, 2018
1 parent 87fa987 commit 870d167
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 46 deletions.
97 changes: 55 additions & 42 deletions cucumber-expressions/go/cucumber_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,31 @@ import (
"strings"
)

var ESCAPE_REGEXP = regexp.MustCompile(`([\\^[$.|?*+])`)
var PARAMETER_REGEXP = regexp.MustCompile(`(\\\\\\\\)?{([^}]+)}`)
var OPTIONAL_REGEXP = regexp.MustCompile(`(\\\\\\\\)?\([^)]+\)`)
var ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = regexp.MustCompile(`([^\s^/]+)((\/[^\s^/]+)+)`)
var DOUBLE_ESCAPE = `\\\\`

type CucumberExpression struct {
expression string
source string
parameterTypes []*ParameterType
treeRegexp *TreeRegexp
}

func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (*CucumberExpression, error) {
ESCAPE_REGEXP := regexp.MustCompile(`([\\^[$.|?*+])`)
PARAMETER_REGEXP := regexp.MustCompile("{([^}]+)}")
OPTIONAL_REGEXP := regexp.MustCompile(`(\\\\\\\\)?\([^)]+\)`)
ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP := regexp.MustCompile(`([^\s^/]+)((\/[^\s^/]+)+)`)

result := &CucumberExpression{expression: expression}
parameterTypes := []*ParameterType{}
r := "^"
matchOffset := 0

// Does not include (){} because they have special meaning
expression = ESCAPE_REGEXP.ReplaceAllString(expression, "\\$1")

// Create non-capturing, optional capture groups from parenthesis
expression = OPTIONAL_REGEXP.ReplaceAllStringFunc(expression, func(match string) string {
if strings.HasPrefix(match, "\\\\\\\\") {
return fmt.Sprintf(`\(%s\)`, match[5:len(match)-1])
}
return fmt.Sprintf("(?:%s)?", match[1:len(match)-1])
})

expression = ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.ReplaceAllStringFunc(expression, func(match string) string {
return fmt.Sprintf("(?:%s)", strings.Replace(match, "/", "|", -1))
})
result := &CucumberExpression{source: expression}

matches := PARAMETER_REGEXP.FindAllStringSubmatchIndex(expression[matchOffset:], -1)
for _, indicies := range matches {
typeName := expression[indicies[2]:indicies[3]]
parameterType := parameterTypeRegistry.LookupByTypeName(typeName)
if parameterType == nil {
return nil, NewUndefinedParameterTypeError(typeName)
}
parameterTypes = append(parameterTypes, parameterType)
text := expression[matchOffset:indicies[0]]
captureRegexp := buildCaptureRegexp(parameterType.regexps)
matchOffset = indicies[1]
r += text
r += captureRegexp
expression = result.processEscapes(expression)
expression = result.processOptional(expression)
expression = result.processAlteration(expression)
expression, err := result.processParameters(expression, parameterTypeRegistry)
if err != nil {
return nil, err
}
expression = "^" + expression + "$"

r += expression[matchOffset:] + "$"
result.parameterTypes = parameterTypes
result.treeRegexp = NewTreeRegexp(regexp.MustCompile(r))
result.treeRegexp = NewTreeRegexp(regexp.MustCompile(expression))
return result, nil
}

Expand All @@ -68,7 +43,45 @@ func (c *CucumberExpression) Regexp() *regexp.Regexp {
}

func (c *CucumberExpression) Source() string {
return c.expression
return c.source
}

func (c *CucumberExpression) processEscapes(expression string) string {
return ESCAPE_REGEXP.ReplaceAllString(expression, `\$1`)
}

func (c *CucumberExpression) processOptional(expression string) string {
return OPTIONAL_REGEXP.ReplaceAllStringFunc(expression, func(match string) string {
if strings.HasPrefix(match, DOUBLE_ESCAPE) {
return fmt.Sprintf(`\(%s\)`, match[5:len(match)-1])
}
return fmt.Sprintf("(?:%s)?", match[1:len(match)-1])
})
}

func (c *CucumberExpression) processAlteration(expression string) string {
return ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.ReplaceAllStringFunc(expression, func(match string) string {
return fmt.Sprintf("(?:%s)", strings.Replace(match, "/", "|", -1))
})
}

func (c *CucumberExpression) processParameters(expression string, parameterTypeRegistry *ParameterTypeRegistry) (string, error) {
var err error
result := PARAMETER_REGEXP.ReplaceAllStringFunc(expression, func(match string) string {
if strings.HasPrefix(match, DOUBLE_ESCAPE) {
return fmt.Sprintf(`\{%s\}`, match[5:len(match)-1])
}

typeName := match[1 : len(match)-1]
parameterType := parameterTypeRegistry.LookupByTypeName(typeName)
if parameterType == nil {
err = NewUndefinedParameterTypeError(typeName)
return match
}
c.parameterTypes = append(c.parameterTypes, parameterType)
return buildCaptureRegexp(parameterType.regexps)
})
return result, err
}

func buildCaptureRegexp(regexps []*regexp.Regexp) string {
Expand Down
10 changes: 6 additions & 4 deletions cucumber-expressions/go/cucumber_expression_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (c *CucumberExpressionGenerator) GenerateExpressions(text string) []*Genera
})

parameterTypeCombinations = append(parameterTypeCombinations, parameterTypes)
expressionTemplate += escapeForUtilFormat(text[pos:bestParameterTypeMatcher.Start()]) + "{%s}"
expressionTemplate += escape(text[pos:bestParameterTypeMatcher.Start()]) + "{%s}"
pos = bestParameterTypeMatcher.Start() + len(bestParameterTypeMatcher.Group())
} else {
break
Expand All @@ -72,7 +72,7 @@ func (c *CucumberExpressionGenerator) GenerateExpressions(text string) []*Genera
break
}
}
expressionTemplate += escapeForUtilFormat(text[pos:])
expressionTemplate += escape(text[pos:])
return NewCombinatorialGeneratedExpressionFactory(expressionTemplate, parameterTypeCombinations).GenerateExpressions()
}

Expand All @@ -94,6 +94,8 @@ func (c *CucumberExpressionGenerator) createParameterTypeMatchers2(parameterType
return result
}

func escapeForUtilFormat(s string) string {
return strings.Replace(s, "%", "%%", -1)
func escape(s string) string {
result := strings.Replace(s, "%", "%%", -1)
result = strings.Replace(result, `(`, `\(`, -1)
return strings.Replace(result, `{`, `\{`, -1)
}
8 changes: 8 additions & 0 deletions cucumber-expressions/go/cucumber_expression_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ func TestCucumberExpressionGeneratory(t *testing.T) {
assertExpression(t, "hello", []string{}, "hello")
})

t.Run("generates expression with escaped left parenthesis", func(t *testing.T) {
assertExpression(t, `\(iii)`, []string{}, "(iii)")
})

t.Run("generates expression with escaped left curly brace", func(t *testing.T) {
assertExpression(t, `\{iii}`, []string{}, "{iii}")
})

t.Run("generates expression for int float arg", func(t *testing.T) {
assertExpression(
t,
Expand Down

0 comments on commit 870d167

Please sign in to comment.