parse scenario or background steps
Этот коммит содержится в:
		
							родитель
							
								
									e7ae301947
								
							
						
					
					
						коммит
						5a3b979e01
					
				
					 3 изменённых файлов: 166 добавлений и 19 удалений
				
			
		
							
								
								
									
										59
									
								
								gherkin/ast.go
									
										
									
									
									
										Обычный файл
									
								
							
							
						
						
									
										59
									
								
								gherkin/ast.go
									
										
									
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,59 @@ | ||||||
|  | package gherkin | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"github.com/l3pp4rd/behat/gherkin/lexer" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type item struct { | ||||||
|  | 	next, prev *item | ||||||
|  | 	value      *lexer.Token | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AST struct { | ||||||
|  | 	head, tail *item | ||||||
|  | 	mut        *sync.Mutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newAST() *AST { | ||||||
|  | 	return &AST{mut: &sync.Mutex{}} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *AST) addTail(t *lexer.Token) *item { | ||||||
|  | 	l.mut.Lock() | ||||||
|  | 	defer l.mut.Unlock() | ||||||
|  | 
 | ||||||
|  | 	it := &item{next: nil, prev: l.tail, value: t} | ||||||
|  | 	if l.head == nil { | ||||||
|  | 		l.head = it | ||||||
|  | 	} else { | ||||||
|  | 		l.tail.next = it | ||||||
|  | 	} | ||||||
|  | 	l.tail = it | ||||||
|  | 	return l.tail | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *AST) addBefore(t *lexer.Token, i *item) *item { | ||||||
|  | 	l.mut.Lock() | ||||||
|  | 	defer l.mut.Unlock() | ||||||
|  | 
 | ||||||
|  | 	it := &item{next: i, prev: i.prev, value: t} | ||||||
|  | 	i.prev = it | ||||||
|  | 	if it.prev == nil { | ||||||
|  | 		l.head = it | ||||||
|  | 	} | ||||||
|  | 	return it | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *AST) addAfter(t *lexer.Token, i *item) *item { | ||||||
|  | 	l.mut.Lock() | ||||||
|  | 	defer l.mut.Unlock() | ||||||
|  | 
 | ||||||
|  | 	it := &item{next: i.next, prev: i, value: t} | ||||||
|  | 	i.next = it | ||||||
|  | 	if it.next == nil { | ||||||
|  | 		l.tail = it | ||||||
|  | 	} | ||||||
|  | 	return it | ||||||
|  | } | ||||||
|  | @ -20,6 +20,8 @@ const ( | ||||||
| 	FEATURE | 	FEATURE | ||||||
| 	BACKGROUND | 	BACKGROUND | ||||||
| 	SCENARIO | 	SCENARIO | ||||||
|  | 
 | ||||||
|  | 	steps | ||||||
| 	GIVEN | 	GIVEN | ||||||
| 	WHEN | 	WHEN | ||||||
| 	THEN | 	THEN | ||||||
|  |  | ||||||
							
								
								
									
										124
									
								
								gherkin/parse.go
									
										
									
									
									
								
							
							
						
						
									
										124
									
								
								gherkin/parse.go
									
										
									
									
									
								
							|  | @ -2,7 +2,8 @@ package gherkin | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"fmt" | ||||||
|  | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/l3pp4rd/behat/gherkin/lexer" | 	"github.com/l3pp4rd/behat/gherkin/lexer" | ||||||
|  | @ -11,6 +12,7 @@ import ( | ||||||
| type Tag string | type Tag string | ||||||
| 
 | 
 | ||||||
| type Scenario struct { | type Scenario struct { | ||||||
|  | 	Title string | ||||||
| 	Steps []*Step | 	Steps []*Step | ||||||
| 	Tags  []Tag | 	Tags  []Tag | ||||||
| } | } | ||||||
|  | @ -40,64 +42,148 @@ type Feature struct { | ||||||
| 	Scenarios   []*Scenario | 	Scenarios   []*Scenario | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var ErrNotFeature = errors.New("expected a file to begin with a feature definition") | var steps = []lexer.TokenType{ | ||||||
|  | 	lexer.GIVEN, | ||||||
|  | 	lexer.WHEN, | ||||||
|  | 	lexer.THEN, | ||||||
|  | 	lexer.AND, | ||||||
|  | 	lexer.BUT, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var ErrEmpty = errors.New("the feature file is empty") | var ErrEmpty = errors.New("the feature file is empty") | ||||||
| var ErrTagsNextToFeature = errors.New("tags must be a single line next to a feature definition") |  | ||||||
| var ErrSingleBackground = errors.New("there can only be a single background section") |  | ||||||
| 
 | 
 | ||||||
| type parser struct { | type parser struct { | ||||||
| 	lx *lexer.Lexer | 	lx   *lexer.Lexer | ||||||
|  | 	path string | ||||||
|  | 	ast  *AST | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Parse(r io.Reader) (*Feature, error) { | func Parse(path string) (*Feature, error) { | ||||||
| 	return (parser{lx: lexer.New(r)}).parseFeature() | 	file, err := os.Open(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer file.Close() | ||||||
|  | 
 | ||||||
|  | 	return (&parser{ | ||||||
|  | 		lx:   lexer.New(file), | ||||||
|  | 		path: path, | ||||||
|  | 		ast:  newAST(), | ||||||
|  | 	}).parseFeature() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p parser) parseFeature() (*Feature, error) { | // reads tokens into AST and skips comments or new lines | ||||||
| 	var tok *lexer.Token = p.lx.Next(lexer.COMMENT, lexer.NEW_LINE) | func (p *parser) next() *lexer.Token { | ||||||
|  | 	tok := p.lx.Next() | ||||||
|  | 	p.ast.addTail(tok) | ||||||
|  | 	if tok.OfType(lexer.COMMENT, lexer.NEW_LINE) { | ||||||
|  | 		return p.next() | ||||||
|  | 	} | ||||||
|  | 	return tok | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // peaks into next token, skips comments or new lines | ||||||
|  | func (p *parser) peek() *lexer.Token { | ||||||
|  | 	if tok := p.lx.Peek(); tok.OfType(lexer.COMMENT, lexer.NEW_LINE) { | ||||||
|  | 		p.lx.Next() | ||||||
|  | 	} | ||||||
|  | 	return p.peek() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *parser) err(s string, l int) error { | ||||||
|  | 	return fmt.Errorf("%s on %s:%d", s, p.path, l) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *parser) parseFeature() (*Feature, error) { | ||||||
|  | 	var tok *lexer.Token = p.next() | ||||||
| 	if tok.Type == lexer.EOF { | 	if tok.Type == lexer.EOF { | ||||||
| 		return nil, ErrEmpty | 		return nil, ErrEmpty | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ft := &Feature{} | 	ft := &Feature{} | ||||||
| 	if tok.Type == lexer.TAGS { | 	if tok.Type == lexer.TAGS { | ||||||
| 		if p.lx.Peek().Type != lexer.FEATURE { | 		if p.peek().Type != lexer.FEATURE { | ||||||
| 			return ft, ErrTagsNextToFeature | 			return ft, p.err("tags must be a single line next to a feature definition", tok.Line) | ||||||
| 		} | 		} | ||||||
| 		ft.Tags = p.parseTags(tok.Value) | 		ft.Tags = p.parseTags(tok.Value) | ||||||
| 		tok = p.lx.Next() | 		tok = p.next() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if tok.Type != lexer.FEATURE { | 	if tok.Type != lexer.FEATURE { | ||||||
| 		return ft, ErrNotFeature | 		return ft, p.err("expected a file to begin with a feature definition, but got '"+tok.Type.String()+"' instead", tok.Line) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ft.Title = tok.Value | 	ft.Title = tok.Value | ||||||
| 	var desc []string | 	var desc []string | ||||||
| 	for ; p.lx.Peek().Type == lexer.TEXT; tok = p.lx.Next() { | 	for ; p.peek().Type == lexer.TEXT; tok = p.next() { | ||||||
| 		desc = append(desc, tok.Value) | 		desc = append(desc, tok.Value) | ||||||
| 	} | 	} | ||||||
| 	ft.Description = strings.Join(desc, "\n") | 	ft.Description = strings.Join(desc, "\n") | ||||||
| 
 | 
 | ||||||
| 	tok = p.lx.Next(lexer.COMMENT, lexer.NEW_LINE) | 	tok = p.next() | ||||||
| 	for ; tok.Type != lexer.EOF; p.lx.Next(lexer.COMMENT, lexer.NEW_LINE) { | 	for tok = p.next(); tok.Type != lexer.EOF; p.next() { | ||||||
|  | 		// there may be a background | ||||||
| 		if tok.Type == lexer.BACKGROUND { | 		if tok.Type == lexer.BACKGROUND { | ||||||
| 			if ft.Background != nil { | 			if ft.Background != nil { | ||||||
| 				return ft, ErrSingleBackground | 				return ft, p.err("there can only be a single background section, but found another", tok.Line) | ||||||
| 			} | 			} | ||||||
| 			ft.Background = p.parseBackground() | 			ft.Background = p.parseBackground() | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | 		// there may be tags before scenario | ||||||
|  | 		sc := &Scenario{} | ||||||
|  | 		if tok.Type == lexer.TAGS { | ||||||
|  | 			sc.Tags, tok = p.parseTags(tok.Value), p.next() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// there must be a scenario otherwise | ||||||
|  | 		if tok.Type != lexer.SCENARIO { | ||||||
|  | 			return ft, p.err("expected a scenario, but got '"+tok.Type.String()+"' instead", tok.Line) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		sc.Title = tok.Value | ||||||
|  | 		p.parseSteps(sc) | ||||||
|  | 		ft.Scenarios = append(ft.Scenarios, sc) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return ft, nil | 	return ft, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p parser) parseBackground() *Background { | func (p *parser) parseBackground() *Background { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p parser) parseTags(s string) (tags []Tag) { | func (p *parser) parseSteps(s *Scenario) error { | ||||||
|  | 	var tok *lexer.Token | ||||||
|  | 	for ; p.peek().OfType(steps...); tok = p.next() { | ||||||
|  | 		step := &Step{Text: tok.Value} | ||||||
|  | 		switch tok.Type { | ||||||
|  | 		case lexer.GIVEN: | ||||||
|  | 			step.Type = Given | ||||||
|  | 		case lexer.WHEN: | ||||||
|  | 			step.Type = When | ||||||
|  | 		case lexer.THEN: | ||||||
|  | 			step.Type = Then | ||||||
|  | 		case lexer.AND: | ||||||
|  | 		case lexer.BUT: | ||||||
|  | 			if len(s.Steps) > 0 { | ||||||
|  | 				step.Type = s.Steps[len(s.Steps)-1].Type | ||||||
|  | 			} else { | ||||||
|  | 				step.Type = Given | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		for ; p.peek().OfType(lexer.TEXT); tok = p.next() { | ||||||
|  | 			step.Text += " " + tok.Value | ||||||
|  | 		} | ||||||
|  | 		// now look for pystring or table | ||||||
|  | 
 | ||||||
|  | 		s.Steps = append(s.Steps, step) | ||||||
|  | 		// return fmt.Errorf("A step was expected, but got: '%s' instead on %s:%d", tok.Type, "file", tok.Line) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *parser) parseTags(s string) (tags []Tag) { | ||||||
| 	for _, tag := range strings.Split(s, " ") { | 	for _, tag := range strings.Split(s, " ") { | ||||||
| 		t := strings.Trim(tag, "@ ") | 		t := strings.Trim(tag, "@ ") | ||||||
| 		if len(t) > 0 { | 		if len(t) > 0 { | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 gedi
						gedi