gherkin 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitattributes +1 -0
- data/History.txt +18 -0
- data/README.rdoc +4 -1
- data/Rakefile +4 -2
- data/VERSION.yml +1 -1
- data/bin/gherkin +1 -1
- data/dotnet/.gitignore +13 -0
- data/features/feature_parser.feature +22 -2
- data/features/native_lexer.feature +1 -1
- data/features/parser_with_native_lexer.feature +1 -1
- data/features/step_definitions/gherkin_steps.rb +2 -6
- data/features/step_definitions/pretty_printer_steps.rb +2 -3
- data/features/steps_parser.feature +1 -1
- data/gherkin.gemspec +53 -24
- data/java/Gherkin.iml +2 -4
- data/java/build.xml +3 -0
- data/java/src/gherkin/FixJava.java +6 -3
- data/java/src/gherkin/I18nLexer.java +48 -0
- data/java/src/gherkin/Listener.java +3 -1
- data/java/src/gherkin/Main.java +17 -0
- data/java/src/gherkin/Parser.java +9 -3
- data/java/src/gherkin/formatter/Argument.java +39 -0
- data/java/src/gherkin/formatter/ArgumentFormat.java +17 -0
- data/java/src/gherkin/formatter/Colors.java +7 -0
- data/java/src/gherkin/formatter/Formatter.java +15 -0
- data/java/src/gherkin/formatter/PrettyFormatter.java +219 -0
- data/java/src/gherkin/parser/StateMachineReader.java +8 -3
- data/java/test/gherkin/formatter/ArgumentTest.java +17 -0
- data/lib/gherkin/csharp_lexer.rb +15 -0
- data/lib/gherkin/format/argument.rb +35 -0
- data/lib/gherkin/format/monochrome_format.rb +9 -0
- data/lib/gherkin/i18n.rb +22 -0
- data/lib/gherkin/i18n.yml +34 -20
- data/lib/gherkin/i18n_lexer.rb +57 -13
- data/lib/gherkin/lexer.rb +9 -18
- data/lib/gherkin/parser.rb +3 -3
- data/lib/gherkin/parser/meta.txt +5 -4
- data/lib/gherkin/parser/root.txt +11 -9
- data/lib/gherkin/parser/steps.txt +4 -3
- data/lib/gherkin/rb_parser.rb +13 -5
- data/lib/gherkin/tools/colors.rb +119 -0
- data/lib/gherkin/tools/files.rb +6 -1
- data/lib/gherkin/tools/pretty_listener.rb +115 -23
- data/ragel/lexer.c.rl.erb +67 -51
- data/ragel/lexer.csharp.rl.erb +240 -0
- data/ragel/lexer.java.rl.erb +27 -18
- data/ragel/lexer.rb.rl.erb +17 -17
- data/ragel/lexer_common.rl.erb +8 -8
- data/spec/gherkin/c_lexer_spec.rb +4 -4
- data/spec/gherkin/csharp_lexer_spec.rb +20 -0
- data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
- data/spec/gherkin/fixtures/complex.feature +2 -0
- data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
- data/spec/gherkin/fixtures/i18n_fr.feature +1 -0
- data/spec/gherkin/fixtures/i18n_no.feature +1 -0
- data/spec/gherkin/fixtures/i18n_zh-CN.feature +1 -0
- data/spec/gherkin/format/argument_spec.rb +28 -0
- data/spec/gherkin/i18n_lexer_spec.rb +4 -4
- data/spec/gherkin/i18n_spec.rb +31 -23
- data/spec/gherkin/java_lexer_spec.rb +4 -3
- data/spec/gherkin/parser_spec.rb +5 -0
- data/spec/gherkin/rb_lexer_spec.rb +4 -2
- data/spec/gherkin/sexp_recorder.rb +1 -1
- data/spec/gherkin/shared/lexer_spec.rb +169 -60
- data/spec/gherkin/shared/py_string_spec.rb +6 -0
- data/spec/gherkin/shared/row_spec.rb +107 -0
- data/spec/gherkin/shared/tags_spec.rb +1 -1
- data/spec/gherkin/tools/colors_spec.rb +19 -0
- data/spec/gherkin/tools/pretty_listener_spec.rb +147 -0
- data/spec/spec_helper.rb +31 -7
- data/tasks/compile.rake +81 -7
- data/tasks/ragel_task.rb +6 -4
- data/tasks/rspec.rake +2 -2
- metadata +104 -41
- data/lib/gherkin/java_lexer.rb +0 -10
- data/spec/gherkin/shared/table_spec.rb +0 -97
data/lib/gherkin/rb_parser.rb
CHANGED
@@ -77,7 +77,7 @@ module Gherkin
|
|
77
77
|
|
78
78
|
def expected
|
79
79
|
allowed = @transition_map[@state].find_all { |_, action| action != "E" }
|
80
|
-
allowed.collect { |state| state[0] }.sort
|
80
|
+
allowed.collect { |state| state[0] }.sort - ['eof']
|
81
81
|
end
|
82
82
|
|
83
83
|
private
|
@@ -100,18 +100,26 @@ module Gherkin
|
|
100
100
|
|
101
101
|
def transition_table(name)
|
102
102
|
state_machine_reader = StateMachineReader.new
|
103
|
-
lexer = Gherkin::
|
103
|
+
lexer = Gherkin::I18nLexer.lexer_class('en', false).new(state_machine_reader)
|
104
104
|
lexer.scan(File.read(File.dirname(__FILE__) + "/parser/#{name}.txt"))
|
105
105
|
state_machine_reader.rows
|
106
106
|
end
|
107
107
|
|
108
108
|
class StateMachineReader
|
109
109
|
attr_reader :rows
|
110
|
-
|
111
|
-
|
110
|
+
|
111
|
+
def initialize
|
112
|
+
@rows = []
|
113
|
+
end
|
114
|
+
|
115
|
+
def row(row, line_number)
|
116
|
+
@rows << row
|
117
|
+
end
|
118
|
+
|
119
|
+
def eof
|
112
120
|
end
|
113
121
|
end
|
114
122
|
|
115
123
|
end
|
116
124
|
end
|
117
|
-
end
|
125
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
module Gherkin
|
4
|
+
module Tools
|
5
|
+
# Defines aliases for coloured output. You don't invoke any methods from this
|
6
|
+
# module directly, but you can change the output colours by defining
|
7
|
+
# a <tt>GHERKIN_COLORS</tt> variable in your shell, very much like how you can
|
8
|
+
# tweak the familiar POSIX command <tt>ls</tt> with
|
9
|
+
# <a href="http://mipsisrisc.com/rambling/2008/06/27/lscolorsls_colors-now-with-linux-support/">$LSCOLORS/$LS_COLORS</a>
|
10
|
+
#
|
11
|
+
# The colours that you can change are:
|
12
|
+
#
|
13
|
+
# * <tt>undefined</tt> - defaults to <tt>yellow</tt>
|
14
|
+
# * <tt>pending</tt> - defaults to <tt>yellow</tt>
|
15
|
+
# * <tt>pending_param</tt> - defaults to <tt>yellow,bold</tt>
|
16
|
+
# * <tt>failed</tt> - defaults to <tt>red</tt>
|
17
|
+
# * <tt>failed_param</tt> - defaults to <tt>red,bold</tt>
|
18
|
+
# * <tt>passed</tt> - defaults to <tt>green</tt>
|
19
|
+
# * <tt>passed_param</tt> - defaults to <tt>green,bold</tt>
|
20
|
+
# * <tt>outline</tt> - defaults to <tt>cyan</tt>
|
21
|
+
# * <tt>outline_param</tt> - defaults to <tt>cyan,bold</tt>
|
22
|
+
# * <tt>skipped</tt> - defaults to <tt>cyan</tt>
|
23
|
+
# * <tt>skipped_param</tt> - defaults to <tt>cyan,bold</tt>
|
24
|
+
# * <tt>comment</tt> - defaults to <tt>grey</tt>
|
25
|
+
# * <tt>tag</tt> - defaults to <tt>cyan</tt>
|
26
|
+
#
|
27
|
+
# For instance, if your shell has a black background and a green font (like the
|
28
|
+
# "Homebrew" settings for OS X' Terminal.app), you may want to override passed
|
29
|
+
# steps to be white instead of green. Examples:
|
30
|
+
#
|
31
|
+
# export GHERKIN_COLORS="passed=white"
|
32
|
+
# export GHERKIN_COLORS="passed=white,bold:passed_param=white,bold,underline"
|
33
|
+
#
|
34
|
+
# (If you're on Windows, use SET instead of export).
|
35
|
+
# To see what colours and effects are available, just run this in your shell:
|
36
|
+
#
|
37
|
+
# ruby -e "require 'rubygems'; require 'term/ansicolor'; puts Term::ANSIColor.attributes"
|
38
|
+
#
|
39
|
+
# Although not listed, you can also use <tt>grey</tt>
|
40
|
+
module Colors
|
41
|
+
include Term::ANSIColor
|
42
|
+
|
43
|
+
ALIASES = Hash.new do |h,k|
|
44
|
+
if k.to_s =~ /(.*)_param/
|
45
|
+
h[$1] + ',bold'
|
46
|
+
end
|
47
|
+
end.merge({
|
48
|
+
'undefined' => 'yellow',
|
49
|
+
'pending' => 'yellow',
|
50
|
+
'failed' => 'red',
|
51
|
+
'passed' => 'green',
|
52
|
+
'outline' => 'cyan',
|
53
|
+
'skipped' => 'cyan',
|
54
|
+
'comments' => 'grey',
|
55
|
+
'tag' => 'cyan'
|
56
|
+
})
|
57
|
+
|
58
|
+
if ENV['GHERKIN_COLORS'] # Example: export GHERKIN_COLORS="passed=red:failed=yellow"
|
59
|
+
ENV['GHERKIN_COLORS'].split(':').each do |pair|
|
60
|
+
a = pair.split('=')
|
61
|
+
ALIASES[a[0]] = a[1]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
ALIASES.each do |method, color|
|
66
|
+
unless method =~ /.*_param/
|
67
|
+
code = <<-EOF
|
68
|
+
def #{method}(string=nil, monochrome=false, &proc)
|
69
|
+
return string if monochrome
|
70
|
+
#{ALIASES[method].split(",").join("(") + "(string, &proc" + ")" * ALIASES[method].split(",").length}
|
71
|
+
end
|
72
|
+
# This resets the colour to the non-param colour
|
73
|
+
def #{method}_param(string=nil, monochrome=false, &proc)
|
74
|
+
return string if monochrome
|
75
|
+
#{ALIASES[method+'_param'].split(",").join("(") + "(string, &proc" + ")" * ALIASES[method+'_param'].split(",").length} + #{ALIASES[method].split(",").join(' + ')}
|
76
|
+
end
|
77
|
+
EOF
|
78
|
+
eval(code)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.define_grey #:nodoc:
|
83
|
+
begin
|
84
|
+
gem 'genki-ruby-terminfo'
|
85
|
+
require 'terminfo'
|
86
|
+
case TermInfo.default_object.tigetnum("colors")
|
87
|
+
when 0
|
88
|
+
raise "Your terminal doesn't support colours"
|
89
|
+
when 1
|
90
|
+
::Term::ANSIColor.coloring = false
|
91
|
+
alias grey white
|
92
|
+
when 2..8
|
93
|
+
alias grey white
|
94
|
+
else
|
95
|
+
define_real_grey
|
96
|
+
end
|
97
|
+
rescue Exception => e
|
98
|
+
if e.class.name == 'TermInfo::TermInfoError'
|
99
|
+
STDERR.puts "*** WARNING ***"
|
100
|
+
STDERR.puts "You have the genki-ruby-terminfo gem installed, but you haven't set your TERM variable."
|
101
|
+
STDERR.puts "Try setting it to TERM=xterm-256color to get grey colour in output"
|
102
|
+
STDERR.puts "\n"
|
103
|
+
alias grey white
|
104
|
+
else
|
105
|
+
define_real_grey
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.define_real_grey #:nodoc:
|
111
|
+
def grey(m) #:nodoc:
|
112
|
+
"\e[90m#{m}\e[0m"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
define_grey
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/gherkin/tools/files.rb
CHANGED
@@ -23,7 +23,12 @@ module Gherkin
|
|
23
23
|
def scan(file, listener)
|
24
24
|
parser = Parser.new(listener, true)
|
25
25
|
lexer = I18nLexer.new(parser)
|
26
|
-
|
26
|
+
begin
|
27
|
+
lexer.scan(IO.read(file))
|
28
|
+
rescue => e
|
29
|
+
e.message << " (#{file})"
|
30
|
+
raise e
|
31
|
+
end
|
27
32
|
end
|
28
33
|
end
|
29
34
|
end
|
@@ -1,10 +1,32 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'gherkin/tools/colors'
|
4
|
+
require 'gherkin/format/monochrome_format'
|
5
|
+
|
2
6
|
module Gherkin
|
3
7
|
module Tools
|
8
|
+
# TODO: Rename to Gherkin::Pretty::PrettyReporter - that's what this class *does*
|
9
|
+
# (The fact that it conforms to the Gherkin Listener interface is secondary)
|
4
10
|
class PrettyListener
|
5
|
-
|
11
|
+
include Gherkin::Tools::Colors
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def new(io, monochrome=false)
|
15
|
+
if defined?(JRUBY_VERSION)
|
16
|
+
require 'gherkin.jar'
|
17
|
+
Java::GherkinFormatter::PrettyFormatter.new(io, monochrome)
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(io, monochrome=false)
|
6
25
|
@io = io
|
26
|
+
@monochrome = monochrome
|
27
|
+
@format = @monochrome ? Format::MonochromeFormat.new : Format::AnsiColorFormat.new
|
7
28
|
@tags = nil
|
29
|
+
@comments = nil
|
8
30
|
end
|
9
31
|
|
10
32
|
def tag(name, line)
|
@@ -13,45 +35,48 @@ module Gherkin
|
|
13
35
|
end
|
14
36
|
|
15
37
|
def comment(content, line)
|
16
|
-
@
|
38
|
+
@comments ||= []
|
39
|
+
@comments << content
|
17
40
|
end
|
18
41
|
|
19
42
|
def feature(keyword, name, line)
|
20
|
-
|
21
|
-
@tags = nil
|
22
|
-
@io.puts "#{tags}#{keyword}: #{indent(name, ' ')}"
|
43
|
+
@io.puts "#{grab_comments!('')}#{grab_tags!('')}#{keyword}: #{indent(name, ' ')}"
|
23
44
|
end
|
24
45
|
|
25
46
|
def background(keyword, name, line)
|
26
|
-
@io.puts "\n #{keyword}: #{indent(name, ' ')}"
|
47
|
+
@io.puts "\n#{grab_comments!(' ')} #{keyword}: #{indent(name, ' ')}"
|
27
48
|
end
|
28
49
|
|
29
|
-
def scenario(keyword, name, line)
|
30
|
-
|
31
|
-
@
|
32
|
-
@io.puts "\n#{tags} #{keyword}: #{indent(name, ' ')}"
|
50
|
+
def scenario(keyword, name, line, location=nil)
|
51
|
+
flush_table
|
52
|
+
@io.puts "\n#{grab_comments!(' ')}#{grab_tags!(' ')} #{keyword}: #{indent(name, ' ')}#{indented_scenario_location!(keyword, name, location)}"
|
33
53
|
end
|
34
54
|
|
35
55
|
def scenario_outline(keyword, name, line)
|
36
|
-
|
37
|
-
@
|
38
|
-
@io.puts "\n#{tags} #{keyword}: #{indent(name, ' ')}"
|
56
|
+
flush_table
|
57
|
+
@io.puts "\n#{grab_comments!(' ')}#{grab_tags!(' ')} #{keyword}: #{indent(name, ' ')}"
|
39
58
|
end
|
40
59
|
|
41
60
|
def examples(keyword, name, line)
|
42
|
-
|
61
|
+
flush_table
|
62
|
+
@io.puts "\n#{grab_comments!(' ')}#{grab_tags!(' ')} #{keyword}: #{indent(name, ' ')}"
|
43
63
|
end
|
44
64
|
|
45
|
-
def step(keyword, name, line)
|
46
|
-
|
65
|
+
def step(keyword, name, line, status=nil, arguments=nil, location=nil)
|
66
|
+
flush_table
|
67
|
+
status_param = "#{status}_param" if status
|
68
|
+
name = Gherkin::Format::Argument.format(name, @format, (arguments || []))
|
69
|
+
#{|arg| status_param ? self.__send__(status_param, arg, @monochrome) : arg} if arguments
|
70
|
+
|
71
|
+
step = "#{keyword}#{indent(name, ' ')}"
|
72
|
+
step = self.__send__(status, step, @monochrome) if status
|
73
|
+
|
74
|
+
@io.puts("#{grab_comments!(' ')} #{step}#{indented_step_location!(location)}")
|
47
75
|
end
|
48
76
|
|
49
|
-
def
|
50
|
-
rows
|
51
|
-
|
52
|
-
rows.each do |table_line|
|
53
|
-
@io.puts ' | ' + table_line.zip(max_lengths).map { |cell, max_length| cell + ' ' * (max_length-cell.unpack("U*").length) }.join(' | ') + ' |'
|
54
|
-
end
|
77
|
+
def row(row, line)
|
78
|
+
@rows ||= []
|
79
|
+
@rows << row
|
55
80
|
end
|
56
81
|
|
57
82
|
def py_string(string, line)
|
@@ -62,7 +87,48 @@ module Gherkin
|
|
62
87
|
raise "SYNTAX ERROR"
|
63
88
|
end
|
64
89
|
|
65
|
-
|
90
|
+
def eof
|
91
|
+
flush_table
|
92
|
+
end
|
93
|
+
|
94
|
+
# This method can be invoked before a #scenario, to ensure location arguments are aligned
|
95
|
+
def steps(steps)
|
96
|
+
@step_lengths = steps.map {|keyword, name| (keyword+name).unpack("U*").length}
|
97
|
+
@max_step_length = @step_lengths.max
|
98
|
+
@step_index = -1
|
99
|
+
end
|
100
|
+
|
101
|
+
def exception(exception)
|
102
|
+
exception_text = "#{exception.message} (#{exception.class})\n#{(exception.backtrace || []).join("\n")}".gsub(/^/, ' ')
|
103
|
+
@io.puts(failed(exception_text, @monochrome))
|
104
|
+
end
|
105
|
+
|
106
|
+
def flush_table(exception=nil, statuses=nil)
|
107
|
+
return if @rows.nil?
|
108
|
+
cell_lengths = @rows.map { |col| col.map { |cell| cell.unpack("U*").length }}
|
109
|
+
max_lengths = cell_lengths.transpose.map { |col_lengths| col_lengths.max }.flatten
|
110
|
+
|
111
|
+
@rows.each_with_index do |row, i|
|
112
|
+
j = -1
|
113
|
+
@io.puts ' | ' + row.zip(max_lengths).map { |cell, max_length|
|
114
|
+
j += 1
|
115
|
+
color(cell, statuses, j) + ' ' * (max_length - cell_lengths[i][j])
|
116
|
+
}.join(' | ') + ' |'
|
117
|
+
exception(exception) if exception
|
118
|
+
end
|
119
|
+
@rows = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def color(cell, statuses, col)
|
125
|
+
if statuses
|
126
|
+
self.__send__(statuses[col], cell, @monochrome) + (@monochrome ? '' : reset)
|
127
|
+
else
|
128
|
+
cell
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
66
132
|
if(RUBY_VERSION =~ /^1\.9/)
|
67
133
|
START = /#{"^".encode('UTF-8')}/
|
68
134
|
NL = Regexp.new("\n".encode('UTF-8'))
|
@@ -79,6 +145,32 @@ module Gherkin
|
|
79
145
|
s
|
80
146
|
end.join("\n")
|
81
147
|
end
|
148
|
+
|
149
|
+
def grab_tags!(indent)
|
150
|
+
tags = @tags ? indent + @tags.join(' ') + "\n" : ''
|
151
|
+
@tags = nil
|
152
|
+
tags
|
153
|
+
end
|
154
|
+
|
155
|
+
def grab_comments!(indent)
|
156
|
+
comments = @comments ? indent + @comments.join("\n#{indent}") + "\n" : ''
|
157
|
+
@comments = nil
|
158
|
+
comments
|
159
|
+
end
|
160
|
+
|
161
|
+
def indented_scenario_location!(keyword, name, location)
|
162
|
+
return "" if location.nil?
|
163
|
+
l = (keyword+name).unpack("U*").length
|
164
|
+
@max_step_length = [@max_step_length, l].max
|
165
|
+
indent = @max_step_length - l
|
166
|
+
' ' * indent + ' ' + comments("# #{location}", @monochrome)
|
167
|
+
end
|
168
|
+
|
169
|
+
def indented_step_location!(location)
|
170
|
+
return "" if location.nil?
|
171
|
+
indent = @max_step_length - @step_lengths[@step_index+=1]
|
172
|
+
' ' * indent + ' ' + comments("# #{location}", @monochrome)
|
173
|
+
end
|
82
174
|
end
|
83
175
|
end
|
84
176
|
end
|
data/ragel/lexer.c.rl.erb
CHANGED
@@ -14,12 +14,17 @@
|
|
14
14
|
#ifdef HAVE_RUBY_ENCODING_H
|
15
15
|
#include <ruby/encoding.h>
|
16
16
|
#define ENCODED_STR_NEW(ptr, len) \
|
17
|
-
rb_enc_str_new(ptr, len, rb_utf8_encoding())
|
17
|
+
rb_enc_str_new(ptr, len, rb_utf8_encoding())
|
18
18
|
#else
|
19
19
|
#define ENCODED_STR_NEW(ptr, len) \
|
20
|
-
rb_str_new(ptr, len)
|
20
|
+
rb_str_new(ptr, len)
|
21
21
|
#endif
|
22
22
|
|
23
|
+
#define LF_FLAG 0
|
24
|
+
#define CRLF_FLAG 1
|
25
|
+
#define LF "\n"
|
26
|
+
#define CRLF "\r\n"
|
27
|
+
|
23
28
|
#ifndef RSTRING_PTR
|
24
29
|
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
25
30
|
#endif
|
@@ -39,23 +44,22 @@ typedef struct lexer_state {
|
|
39
44
|
int line_number;
|
40
45
|
int current_line;
|
41
46
|
int start_col;
|
47
|
+
int eol;
|
42
48
|
size_t mark;
|
43
49
|
size_t keyword_start;
|
44
50
|
size_t keyword_end;
|
45
51
|
size_t next_keyword_start;
|
46
52
|
size_t content_start;
|
47
53
|
size_t content_end;
|
48
|
-
size_t field_len;
|
49
54
|
size_t query_start;
|
50
55
|
size_t last_newline;
|
51
56
|
size_t final_newline;
|
52
57
|
} lexer_state;
|
53
58
|
|
54
59
|
static VALUE mGherkin;
|
55
|
-
static VALUE mLexer;
|
56
60
|
static VALUE mCLexer;
|
57
61
|
static VALUE cI18nLexer;
|
58
|
-
static VALUE
|
62
|
+
static VALUE rb_eGherkinLexingError;
|
59
63
|
|
60
64
|
#define LEN(AT, P) (P - data - lexer->AT)
|
61
65
|
#define MARK(M, P) (lexer->M = (P) - data)
|
@@ -65,16 +69,16 @@ static VALUE rb_eGherkinLexerError;
|
|
65
69
|
store_kw_con(listener, # EVENT, \
|
66
70
|
PTR_TO(keyword_start), LEN(keyword_start, PTR_TO(keyword_end - 1)), \
|
67
71
|
PTR_TO(content_start), LEN(content_start, PTR_TO(content_end)), \
|
68
|
-
lexer->current_line); \
|
72
|
+
lexer->current_line, lexer->eol); \
|
69
73
|
if (lexer->content_end != 0) { \
|
70
74
|
p = PTR_TO(content_end - 1); \
|
71
75
|
} \
|
72
|
-
lexer->content_end = 0
|
76
|
+
lexer->content_end = 0
|
73
77
|
|
74
78
|
#define STORE_ATTR(ATTR) \
|
75
79
|
store_attr(listener, # ATTR, \
|
76
80
|
PTR_TO(content_start), LEN(content_start, p), \
|
77
|
-
lexer->line_number)
|
81
|
+
lexer->line_number)
|
78
82
|
|
79
83
|
%%{
|
80
84
|
machine lexer;
|
@@ -102,39 +106,39 @@ static VALUE rb_eGherkinLexerError;
|
|
102
106
|
}
|
103
107
|
|
104
108
|
action store_feature_content {
|
105
|
-
STORE_KW_END_CON(feature)
|
109
|
+
STORE_KW_END_CON(feature);
|
106
110
|
}
|
107
111
|
|
108
112
|
action store_background_content {
|
109
|
-
STORE_KW_END_CON(background)
|
113
|
+
STORE_KW_END_CON(background);
|
110
114
|
}
|
111
115
|
|
112
116
|
action store_scenario_content {
|
113
|
-
STORE_KW_END_CON(scenario)
|
117
|
+
STORE_KW_END_CON(scenario);
|
114
118
|
}
|
115
119
|
|
116
120
|
action store_scenario_outline_content {
|
117
|
-
STORE_KW_END_CON(scenario_outline)
|
121
|
+
STORE_KW_END_CON(scenario_outline);
|
118
122
|
}
|
119
123
|
|
120
124
|
action store_examples_content {
|
121
|
-
STORE_KW_END_CON(examples)
|
125
|
+
STORE_KW_END_CON(examples);
|
122
126
|
}
|
123
127
|
|
124
128
|
action store_step_content {
|
125
129
|
store_kw_con(listener, "step",
|
126
130
|
PTR_TO(keyword_start), LEN(keyword_start, PTR_TO(keyword_end)),
|
127
131
|
PTR_TO(content_start), LEN(content_start, p),
|
128
|
-
lexer->current_line);
|
132
|
+
lexer->current_line, lexer->eol);
|
129
133
|
}
|
130
134
|
|
131
135
|
action store_comment_content {
|
132
|
-
STORE_ATTR(comment)
|
136
|
+
STORE_ATTR(comment);
|
133
137
|
lexer->mark = 0;
|
134
138
|
}
|
135
139
|
|
136
140
|
action store_tag_content {
|
137
|
-
STORE_ATTR(tag)
|
141
|
+
STORE_ATTR(tag);
|
138
142
|
lexer->mark = 0;
|
139
143
|
}
|
140
144
|
|
@@ -164,11 +168,10 @@ static VALUE rb_eGherkinLexerError;
|
|
164
168
|
MARK(content_end, p);
|
165
169
|
}
|
166
170
|
|
167
|
-
action
|
171
|
+
action start_row {
|
168
172
|
p = p - 1;
|
169
173
|
lexer->current_line = lexer->line_number;
|
170
|
-
|
171
|
-
rb_ary_clear(current_row);
|
174
|
+
current_row = rb_ary_new();
|
172
175
|
}
|
173
176
|
|
174
177
|
action begin_cell_content {
|
@@ -176,29 +179,20 @@ static VALUE rb_eGherkinLexerError;
|
|
176
179
|
}
|
177
180
|
|
178
181
|
action store_cell_content {
|
179
|
-
VALUE con =
|
180
|
-
con = ENCODED_STR_NEW(PTR_TO(content_start), LEN(content_start, p));
|
182
|
+
VALUE con = ENCODED_STR_NEW(PTR_TO(content_start), LEN(content_start, p));
|
181
183
|
rb_funcall(con, rb_intern("strip!"), 0);
|
182
184
|
|
183
185
|
rb_ary_push(current_row, con);
|
184
186
|
}
|
185
187
|
|
186
|
-
action start_row {
|
187
|
-
current_row = rb_ary_new();
|
188
|
-
}
|
189
|
-
|
190
188
|
action store_row {
|
191
|
-
|
192
|
-
}
|
193
|
-
|
194
|
-
action store_table {
|
195
|
-
rb_funcall(listener, rb_intern("table"), 2, rows, INT2FIX(lexer->current_line));
|
189
|
+
rb_funcall(listener, rb_intern("row"), 2, current_row, INT2FIX(lexer->current_line));
|
196
190
|
}
|
197
191
|
|
198
192
|
action end_feature {
|
199
193
|
if (cs < lexer_first_final) {
|
200
194
|
if (raise_lexer_error != NULL) {
|
201
|
-
|
195
|
+
size_t count = 0;
|
202
196
|
int newstr_count = 0;
|
203
197
|
size_t len;
|
204
198
|
const char *buff;
|
@@ -229,8 +223,10 @@ static VALUE rb_eGherkinLexerError;
|
|
229
223
|
|
230
224
|
int line = lexer->line_number;
|
231
225
|
lexer_init(lexer); // Re-initialize so we can scan again with the same lexer
|
232
|
-
raise_lexer_error(
|
226
|
+
raise_lexer_error(newstr, line);
|
233
227
|
}
|
228
|
+
} else {
|
229
|
+
rb_funcall(listener, rb_intern("eof"), 0);
|
234
230
|
}
|
235
231
|
}
|
236
232
|
|
@@ -251,28 +247,28 @@ strip_i(VALUE str, VALUE ary)
|
|
251
247
|
}
|
252
248
|
|
253
249
|
static VALUE
|
254
|
-
multiline_strip(VALUE text)
|
250
|
+
multiline_strip(VALUE text, int eol)
|
255
251
|
{
|
256
252
|
VALUE map = rb_ary_new();
|
257
253
|
VALUE split = rb_str_split(text, "\n");
|
258
254
|
|
259
255
|
rb_iterate(rb_each, split, strip_i, map);
|
260
256
|
|
261
|
-
return rb_ary_join(split, rb_str_new2(
|
257
|
+
return rb_ary_join(split, rb_str_new2( \
|
258
|
+
eol == CRLF_FLAG ? CRLF : LF ));
|
262
259
|
}
|
263
260
|
|
264
261
|
static void
|
265
262
|
store_kw_con(VALUE listener, const char * event_name,
|
266
263
|
const char * keyword_at, size_t keyword_length,
|
267
264
|
const char * at, size_t length,
|
268
|
-
int current_line)
|
265
|
+
int current_line, int eol)
|
269
266
|
{
|
270
267
|
VALUE con = Qnil, kw = Qnil;
|
271
268
|
kw = ENCODED_STR_NEW(keyword_at, keyword_length);
|
272
269
|
con = ENCODED_STR_NEW(at, length);
|
273
|
-
con = multiline_strip(con);
|
270
|
+
con = multiline_strip(con, eol);
|
274
271
|
rb_funcall(con, rb_intern("strip!"), 0);
|
275
|
-
rb_funcall(kw, rb_intern("strip!"), 0);
|
276
272
|
rb_funcall(listener, rb_intern(event_name), 3, kw, con, INT2FIX(current_line));
|
277
273
|
}
|
278
274
|
|
@@ -296,14 +292,30 @@ store_pystring_content(VALUE listener,
|
|
296
292
|
char pat[32];
|
297
293
|
snprintf(pat, 32, "^ {0,%d}", start_col);
|
298
294
|
VALUE re = rb_reg_regcomp(rb_str_new2(pat));
|
295
|
+
VALUE re2 = rb_reg_regcomp(rb_str_new2("\r\\Z"));
|
299
296
|
rb_funcall(con, rb_intern("gsub!"), 2, re, rb_str_new2(""));
|
297
|
+
rb_funcall(con, rb_intern("sub!"), 2, re2, rb_str_new2(""));
|
300
298
|
rb_funcall(listener, rb_intern("py_string"), 2, con, INT2FIX(current_line));
|
301
299
|
}
|
302
300
|
|
303
301
|
static void
|
304
|
-
raise_lexer_error(
|
302
|
+
raise_lexer_error(const char * at, int line)
|
305
303
|
{
|
306
|
-
rb_raise(
|
304
|
+
rb_raise(rb_eGherkinLexingError, "Lexing error on line %d: '%s'.", line, at);
|
305
|
+
}
|
306
|
+
|
307
|
+
static int
|
308
|
+
count_char(char char_to_count, char *str) {
|
309
|
+
|
310
|
+
int count = 0;
|
311
|
+
int i = 0;
|
312
|
+
while(str[i] != '\0') {
|
313
|
+
if(str[i] == char_to_count) {
|
314
|
+
count++;
|
315
|
+
}
|
316
|
+
i++;
|
317
|
+
}
|
318
|
+
return count;
|
307
319
|
}
|
308
320
|
|
309
321
|
static void lexer_init(lexer_state *lexer) {
|
@@ -311,7 +323,6 @@ static void lexer_init(lexer_state *lexer) {
|
|
311
323
|
lexer->content_end = 0;
|
312
324
|
lexer->content_len = 0;
|
313
325
|
lexer->mark = 0;
|
314
|
-
lexer->field_len = 0;
|
315
326
|
lexer->keyword_start = 0;
|
316
327
|
lexer->keyword_end = 0;
|
317
328
|
lexer->next_keyword_start = 0;
|
@@ -319,6 +330,7 @@ static void lexer_init(lexer_state *lexer) {
|
|
319
330
|
lexer->last_newline = 0;
|
320
331
|
lexer->final_newline = 0;
|
321
332
|
lexer->start_col = 0;
|
333
|
+
lexer->eol = LF_FLAG;
|
322
334
|
}
|
323
335
|
|
324
336
|
static VALUE CLexer_alloc(VALUE klass)
|
@@ -348,41 +360,45 @@ static VALUE CLexer_scan(VALUE self, VALUE input)
|
|
348
360
|
lexer_state *lexer = NULL;
|
349
361
|
DATA_GET(self, lexer_state, lexer);
|
350
362
|
|
363
|
+
|
351
364
|
VALUE input_copy = rb_str_dup(input);
|
365
|
+
|
352
366
|
rb_str_append(input_copy, rb_str_new2("\n%_FEATURE_END_%"));
|
353
367
|
char *data = RSTRING_PTR(input_copy);
|
354
|
-
|
368
|
+
size_t len = RSTRING_LEN(input_copy);
|
369
|
+
|
370
|
+
if (count_char('\r', data) > (count_char('\n', data) / 2)) {
|
371
|
+
lexer->eol = CRLF_FLAG;
|
372
|
+
}
|
355
373
|
|
356
374
|
if (len == 0) {
|
357
|
-
rb_raise(
|
375
|
+
rb_raise(rb_eGherkinLexingError, "No content to lex.");
|
358
376
|
} else {
|
377
|
+
|
359
378
|
const char *p, *pe, *eof;
|
360
379
|
int cs = 0;
|
361
380
|
|
362
381
|
VALUE listener = rb_iv_get(self, "@listener");
|
363
|
-
VALUE
|
364
|
-
|
365
|
-
|
382
|
+
VALUE current_row = Qnil;
|
383
|
+
|
366
384
|
p = data;
|
367
385
|
pe = data + len;
|
368
386
|
eof = pe;
|
369
387
|
|
370
388
|
assert(*pe == '\0' && "pointer does not end on NULL");
|
371
|
-
assert(pe - p == len && "pointers aren't same distance");
|
372
389
|
|
373
390
|
%% write init;
|
374
391
|
%% write exec;
|
375
|
-
|
392
|
+
|
376
393
|
assert(p <= pe && "data overflow after parsing execute");
|
377
394
|
assert(lexer->content_start <= len && "content starts after data end");
|
378
395
|
assert(lexer->mark < len && "mark is after data end");
|
379
|
-
assert(lexer->field_len <= len && "field has length longer than the whole data");
|
380
396
|
|
381
397
|
// Reset lexer by re-initializing the whole thing
|
382
398
|
lexer_init(lexer);
|
383
399
|
|
384
400
|
if (cs == lexer_error) {
|
385
|
-
rb_raise(
|
401
|
+
rb_raise(rb_eGherkinLexingError, "Invalid format, lexing fails.");
|
386
402
|
} else {
|
387
403
|
return Qtrue;
|
388
404
|
}
|
@@ -392,8 +408,7 @@ static VALUE CLexer_scan(VALUE self, VALUE input)
|
|
392
408
|
void Init_gherkin_lexer_<%= @i18n.sanitized_key %>()
|
393
409
|
{
|
394
410
|
mGherkin = rb_define_module("Gherkin");
|
395
|
-
|
396
|
-
rb_eGherkinLexerError = rb_const_get(mLexer, rb_intern("LexingError"));
|
411
|
+
rb_eGherkinLexingError = rb_const_get(mGherkin, rb_intern("LexingError"));
|
397
412
|
|
398
413
|
mCLexer = rb_define_module_under(mGherkin, "CLexer");
|
399
414
|
cI18nLexer = rb_define_class_under(mCLexer, "<%= @i18n.sanitized_key.capitalize %>", rb_cObject);
|
@@ -401,3 +416,4 @@ void Init_gherkin_lexer_<%= @i18n.sanitized_key %>()
|
|
401
416
|
rb_define_method(cI18nLexer, "initialize", CLexer_init, 1);
|
402
417
|
rb_define_method(cI18nLexer, "scan", CLexer_scan, 1);
|
403
418
|
}
|
419
|
+
|